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

chore: Test supported python versions #124

Merged
merged 16 commits into from
Aug 31, 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
22 changes: 12 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10.6'
python-version: '3.11'
- name: Checkout
uses: actions/checkout@v3
- name: Install requirements
Expand All @@ -36,20 +36,18 @@ jobs:
if: ${{ failure() || success() }}

tests:
name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }}"
runs-on: ${{ matrix.runs-on }}
name: "🐍 ${{ matrix.python }} • pybind-${{ matrix.pybind11-branch }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
runs-on: [ ubuntu-latest ]
pybind11-branch: [ "master" ]
python:
- "3.11"
- "3.10"
- "3.9"
# - "3.8" # `typing.Annotated` not available
include:
- runs-on: "windows-2022"
python: "3.10"
- "3.8"
- "3.7"
steps:
- uses: actions/checkout@v3

Expand All @@ -66,10 +64,14 @@ jobs:
run: python -m pip install pytest-github-actions-annotate-failures

- name: Install requirements
run: pip install -r requirements-dev.txt
run: pip install -r "./tests/stubs/python-${{ matrix.python }}/requirements.txt"

- name: Install pybind11-stubgen
run: pip install .

- name: Run tests
run: ./tests/run-tests.sh
shell: bash
run: >
./tests/run-tests.sh \
--pybind11-branch "${{ matrix.pybind11-branch }}" \
--stubs-sub-dir "stubs/python-${{ matrix.python }}/pybind11-${{ matrix.pybind11-branch }}" \
2 changes: 0 additions & 2 deletions pybind11_stubgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
)
from pybind11_stubgen.printer import Printer
from pybind11_stubgen.structs import QualifiedName
from pybind11_stubgen.utils import implements
from pybind11_stubgen.writer import Writer


Expand Down Expand Up @@ -179,7 +178,6 @@ def stub_parser_from_args(args) -> IParser:
),
]

@implements(IParser)
class Parser(
*error_handlers, # type: ignore[misc]
FixMissing__future__AnnotationsImport,
Expand Down
28 changes: 25 additions & 3 deletions pybind11_stubgen/parser/interface.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations

import abc
import types
from typing import Any, Protocol, runtime_checkable
from typing import Any

from pybind11_stubgen.parser.errors import ParserError
from pybind11_stubgen.structs import (
Expand All @@ -23,82 +24,103 @@
)


@runtime_checkable
class IParser(Protocol):
class IParser(abc.ABC):
@abc.abstractmethod
def is_print_safe(self, value: Value) -> bool:
...

@abc.abstractmethod
def handle_alias(self, path: QualifiedName, origin: Any) -> Alias | None:
...

@abc.abstractmethod
def handle_attribute(self, path: QualifiedName, attr: Any) -> Attribute | None:
...

@abc.abstractmethod
def handle_bases(
self, path: QualifiedName, bases: tuple[type, ...]
) -> list[QualifiedName]:
...

@abc.abstractmethod
def handle_class(self, path: QualifiedName, class_: type) -> Class | None:
...

@abc.abstractmethod
def handle_class_member(
self, path: QualifiedName, class_: type, obj: Any
) -> Docstring | Alias | Class | list[Method] | Field | Property | None:
...

@abc.abstractmethod
def handle_docstring(self, path: QualifiedName, doc: Any) -> Docstring | None:
...

@abc.abstractmethod
def handle_field(self, path: QualifiedName, field: Any) -> Field | None:
...

@abc.abstractmethod
def handle_function(self, path: QualifiedName, func: Any) -> list[Function]:
...

@abc.abstractmethod
def handle_import(self, path: QualifiedName, origin: Any) -> Import | None:
...

@abc.abstractmethod
def handle_method(self, path: QualifiedName, method: Any) -> list[Method]:
...

@abc.abstractmethod
def handle_module(
self, path: QualifiedName, module: types.ModuleType
) -> Module | None:
...

@abc.abstractmethod
def handle_module_member(
self, path: QualifiedName, module: types.ModuleType, obj: Any
) -> (
Docstring | Import | Alias | Class | list[Function] | Attribute | Module | None
):
...

@abc.abstractmethod
def handle_property(self, path: QualifiedName, prop: Any) -> Property | None:
...

@abc.abstractmethod
def handle_type(self, type_: type) -> QualifiedName:
...

@abc.abstractmethod
def handle_value(self, value: Any) -> Value:
...

@abc.abstractmethod
def parse_args_str(self, args_str: str) -> list[Argument]:
...

@abc.abstractmethod
def parse_annotation_str(
self, annotation_str: str
) -> ResolvedType | InvalidExpression | Value:
...

@abc.abstractmethod
def parse_value_str(self, value: str) -> Value:
...

@abc.abstractmethod
def value_to_repr(self, value: Any) -> str:
...

@abc.abstractmethod
def report_error(self, error: ParserError) -> None:
...

@abc.abstractmethod
def finalize(self):
...
6 changes: 5 additions & 1 deletion pybind11_stubgen/parser/mixins/fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,11 @@ def parse_annotation_str(
or result.name != self.__ndarray_name
or result.parameters is None
or len(result.parameters) != 1
or not isinstance(param := result.parameters[0], ResolvedType)
):
return result
param = result.parameters[0]
if (
not isinstance(param, ResolvedType)
or param.name not in self.numpy_primitive_types
or param.parameters is None
or any(not isinstance(dim, Value) for dim in param.parameters)
Expand Down
20 changes: 15 additions & 5 deletions pybind11_stubgen/parser/mixins/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,12 @@ def is_print_safe(self, value: Any) -> bool:
return False
return True
if inspect.isfunction(value):
module_name = getattr(value, "__module__", None)
qual_name = getattr(value, "__qualname__", None)
if (
(module_name := getattr(value, "__module__", None)) is not None
module_name is not None
and "<" not in module_name
and (qual_name := getattr(value, "__qualname__", None)) is not None
and qual_name is not None
and "<" not in qual_name
):
return True
Expand Down Expand Up @@ -284,7 +286,7 @@ def handle_function(self, path: QualifiedName, func: Any) -> list[Function]:
)
elif not isinstance(annotation, type):
func_args[arg_name].annotation = self.handle_value(annotation)
elif isinstance(annotation, types.GenericAlias):
elif self._is_generic_alias(annotation):
func_args[arg_name].annotation = self.parse_annotation_str(
str(annotation)
)
Expand Down Expand Up @@ -319,6 +321,12 @@ def handle_function(self, path: QualifiedName, func: Any) -> list[Function]:
)
]

def _is_generic_alias(self, annotation: type) -> bool:
generic_alias_t: type | None = getattr(types, "GenericAlias", None)
if generic_alias_t is None:
return False
return isinstance(annotation, generic_alias_t)

def handle_import(self, path: QualifiedName, origin: Any) -> Import | None:
full_name = self._get_full_name(path, origin)
if full_name is None:
Expand Down Expand Up @@ -485,7 +493,8 @@ def parse_args_str(self, args_str: str) -> list[Argument]:
variadic = False
kw_variadic = False

if (stars := match.group("stars")) == "*":
stars = match.group("stars")
if stars == "*":
variadic = True
elif stars == "**":
kw_variadic = True
Expand Down Expand Up @@ -582,7 +591,8 @@ def parse_function_docstring(
return []

if len(doc_lines) < 2 or doc_lines[1] != "Overloaded function.":
if returns_str := match.group("returns"):
returns_str = match.group("returns")
if returns_str is not None:
returns = self.parse_annotation_str(returns_str)
else:
returns = None
Expand Down
4 changes: 3 additions & 1 deletion pybind11_stubgen/printer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import dataclasses
import sys

from pybind11_stubgen.structs import (
Alias,
Expand Down Expand Up @@ -133,7 +134,8 @@ def print_function(self, func: Function) -> list[str]:
kw_only = True
if not pos_only and not arg.pos_only:
pos_only = True
args.append("/")
if sys.version_info[:2] >= (3, 8):
args.append("/")
if not kw_only and arg.kw_only:
kw_only = True
args.append("*")
Expand Down
12 changes: 10 additions & 2 deletions pybind11_stubgen/structs.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from __future__ import annotations

import sys
from dataclasses import dataclass
from dataclasses import field as field_
from typing import Literal, Tuple, Union
from typing import Tuple, Union

Modifier = Literal["static", "class", None]
if sys.version_info[:2] >= (3, 8):
from typing import Literal

Modifier = Literal["static", "class", None]
else:
from typing import Optional

Modifier = Optional[str]


class Identifier(str):
Expand Down
16 changes: 0 additions & 16 deletions pybind11_stubgen/utils.py

This file was deleted.

26 changes: 7 additions & 19 deletions tests/py-demo/demo/pure_python/functions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
from __future__ import annotations

import sys
import typing

if sys.version_info[:2] >= (3, 8):
from .functions_3_8_plus import args_mix

if sys.version_info[:2] >= (3, 9):
from .functions_3_9_plus import generic_alias_annotation


class _Dummy:
@staticmethod
Expand Down Expand Up @@ -29,24 +36,5 @@ def static_method_as_default_arg(callback=_Dummy.foo):
...


def arg_mix(
a: int,
b: float = 0.5,
/,
c: str = "",
*args: int,
x: int = 1,
y=search,
**kwargs: dict[int, str],
):
"""Mix of positional, kw and variadic args

Note:
The `inspect.getfullargspec` does not reflect presence
of pos-only args separator (/)
"""
...


def accept_frozenset(arg: typing.FrozenSet[int | float]) -> int | None:
pass
14 changes: 14 additions & 0 deletions tests/py-demo/demo/pure_python/functions_3_8_plus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import typing


def args_mix(
a: int,
b: float = 0.5,
/,
c: str = "",
*args: int,
x: int = 1,
y=int,
**kwargs: typing.Dict[int, str],
):
...
5 changes: 5 additions & 0 deletions tests/py-demo/demo/pure_python/functions_3_9_plus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def generic_alias_annotation(
a: list[tuple[int]],
b: dict[int, str],
) -> list[float]:
...
Loading