Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FFI type hinting #436

Merged
merged 2 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,15 @@ repos:
hooks:
- id: commitizen
stages: [commit-msg]

- repo: local
hooks:
# Mypy is difficult to run pre-commit's isolated environment as it needs
# to be able to find dependencies.
- id: mypy
name: mypy
entry: hatch run mypy
language: system
types: [python]
exclude: ^(pact|tests)/(?!v3/).*\.py$
stages: [pre-push]
6 changes: 6 additions & 0 deletions pact/v3/_ffi.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import ctypes

import cffi

lib: ctypes.CDLL
ffi: cffi.FFI
54 changes: 28 additions & 26 deletions pact/v3/ffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,15 @@
from __future__ import annotations

import gc
import typing
import warnings
from enum import Enum
from typing import TYPE_CHECKING, List

from ._ffi import ffi, lib # type: ignore[import]

if TYPE_CHECKING:
import cffi
from pathlib import Path

# The follow types are classes defined in the Rust code. Ultimately, a Python
Expand Down Expand Up @@ -530,35 +532,27 @@ def __repr__(self) -> str:
return f"PactSpecification.{self.name}"


class _StringResult(Enum):
class StringResult:
"""
String Result.

[Rust `StringResult`](https://docs.rs/pact_ffi/0.4.9/pact_ffi/mock_server/enum.StringResult.html)
String result.
"""

FAILED = lib.StringResult_Failed
OK = lib.StringResult_Ok

def __str__(self) -> str:
"""
Informal string representation of the String Result.
class _StringResult(Enum):
"""
return self.name
Internal enum from Pact FFI.

def __repr__(self) -> str:
"""
Information-rich string representation of the String Result.
[Rust `StringResult`](https://docs.rs/pact_ffi/0.4.9/pact_ffi/mock_server/enum.StringResult.html)
"""
return f"_StringResultEnum.{self.name}"

FAILED = lib.StringResult_Failed
OK = lib.StringResult_Ok

class StringResult:
"""
String result.
"""
class _StringResultCData:
tag: int
ok: cffi.FFI.CData
failed: cffi.FFI.CData

def __init__(self, cdata: ffi.CData) -> None:
def __init__(self, cdata: cffi.FFI.CData) -> None:
"""
Initialise a new String Result.

Expand All @@ -569,7 +563,7 @@ def __init__(self, cdata: ffi.CData) -> None:
if ffi.typeof(cdata).cname != "struct StringResult":
msg = f"cdata must be a struct StringResult, got {ffi.typeof(cdata).cname}"
raise TypeError(msg)
self._cdata: ffi.CData = cdata
self._cdata = typing.cast(StringResult._StringResultCData, cdata)

def __str__(self) -> str:
"""
Expand All @@ -588,22 +582,25 @@ def is_failed(self) -> bool:
"""
Whether the result is an error.
"""
return self._cdata.tag == _StringResult.FAILED.value
return self._cdata.tag == StringResult._StringResult.FAILED.value

@property
def is_ok(self) -> bool:
"""
Whether the result is ok.
"""
return self._cdata.tag == _StringResult.OK.value
return self._cdata.tag == StringResult._StringResult.OK.value

@property
def text(self) -> str:
"""
The text of the result.
"""
# The specific `.ok` or `.failed` does not matter.
return ffi.string(self._cdata.ok).decode("utf-8")
s = ffi.string(self._cdata.ok)
if isinstance(s, bytes):
return s.decode("utf-8")
return s

def raise_exception(self) -> None:
"""
Expand All @@ -625,7 +622,10 @@ def version() -> str:
Returns:
The version of the pact_ffi library as a string, in the form of `x.y.z`.
"""
return ffi.string(lib.pactffi_version()).decode("utf-8")
v = ffi.string(lib.pactffi_version())
if isinstance(v, bytes):
return v.decode("utf-8")
return v


def init(log_env_var: str) -> None:
Expand Down Expand Up @@ -845,7 +845,9 @@ def get_error_message(length: int = 1024) -> str | None:
if ret >= 0:
# While the documentation says that the return value is the number of bytes
# written, the actually return value is always 0 on success.
if msg := ffi.string(buffer).decode("utf-8"):
if msg := ffi.string(buffer):
if isinstance(msg, bytes):
return msg.decode("utf-8")
return msg
return None
if ret == -1:
Expand Down
Loading