Skip to content

Commit

Permalink
Cythonize (de)serialization methods and enable errors customization (#…
Browse files Browse the repository at this point in the history
…235)

Closures have been converted into classes with one method. They have been regrouped in modules called methods.py. Only methods modules are cythonized.

Cythonization is made by generating a pyx file from the Python file. This approach doesn't require much adaptation to the Python code. Also, it allows optimizations like dynamic dispatch conversion to switch (Python code would have been dirty to with a lot of if-chains).

As constraints validation has been completely refactored to be cythonized too, error customization has been added at the same time as it was simpler.
  • Loading branch information
wyfo authored Nov 3, 2021
1 parent bbc6c4a commit fccfbe3
Show file tree
Hide file tree
Showing 31 changed files with 1,969 additions and 867 deletions.
12 changes: 11 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ on:

jobs:
test:
name: Test ${{ matrix.python-version }}
name: Test ${{ matrix.python-version }}${{ matrix.compile && ' compiled' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', 'pypy3']
compile: [true, false]
exclude:
- python-version: pypy3
compile: true
steps:
- uses: actions/cache@v2
with:
Expand All @@ -24,6 +28,12 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: cythonize
if: matrix.compile
run: |
python -m pip install cython ${{ matrix.compile && (matrix.python-version == '3.6' || matrix.python-version == 'pypy3') && 'dataclasses' || '' }}
python scripts/cythonize.py
python setup.py build_ext --inplace
- name: Install tox
run: |
python -m pip install --upgrade pip
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@ venv.bak/
.idea
__generated__
cov-*
*.c
*.pyx
*.pxd
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This library fulfills the following goals:

- stay as close as possible to the standard library (dataclasses, typing, etc.) — as a consequence we do not need plugins for editors/linters/etc.;
- be adaptable, provide tools to support any types (ORM, etc.);
- avoid dynamic things like using raw strings for attributes name - play nicely with your IDE.
- avoid dynamic things like using raw strings for attributes name play nicely with your IDE.

No known alternative achieves all of this, and apischema is also [faster](https://wyfo.github.io/apischema/performance_and_benchmark) than all of them.

Expand Down
12 changes: 6 additions & 6 deletions apischema/conversions/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,20 @@ def annotated(self, tp: AnyType, annotations: Sequence[Any]) -> Result:
return super().annotated(tp, annotations)
return super().annotated(tp, annotations)

def _union_results(self, alternatives: Iterable[AnyType]) -> Sequence[Result]:
def _union_results(self, types: Iterable[AnyType]) -> Sequence[Result]:
results = []
for alt in alternatives:
for alt in types:
with suppress(Unsupported):
results.append(self.visit(alt))
if not results:
raise Unsupported(Union[tuple(alternatives)])
raise Unsupported(Union[tuple(types)])
return results

def _visited_union(self, results: Sequence[Result]) -> Result:
raise NotImplementedError

def union(self, alternatives: Sequence[AnyType]) -> Result:
return self._visited_union(self._union_results(alternatives))
def union(self, types: Sequence[AnyType]) -> Result:
return self._visited_union(self._union_results(types))

@contextmanager
def _replace_conversion(self, conversion: Optional[AnyConversion]):
Expand Down Expand Up @@ -130,7 +130,7 @@ def visit(self, tp: AnyType) -> Result:
tp, self.default_conversion(get_origin_or_type(tp)) # type: ignore
)
next_conversion = None
if not dynamic and is_subclass(tp, Collection):
if not dynamic and is_subclass(tp, Collection) and not is_subclass(tp, str):
next_conversion = self._conversion
return self.visit_conversion(tp, conversion, dynamic, next_conversion)

Expand Down
Loading

0 comments on commit fccfbe3

Please sign in to comment.