Skip to content

Commit

Permalink
v3.4.1 rc
Browse files Browse the repository at this point in the history
  • Loading branch information
HeWeMel committed Dec 26, 2024
1 parent f018d62 commit f7b7aba
Show file tree
Hide file tree
Showing 60 changed files with 1,486 additions and 1,198 deletions.
4 changes: 3 additions & 1 deletion docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ correctly handles them.
.. _nographs.Weight:
.. code-block:: python
class Weight(Protocol[T]):
T = TypeVar("T")
class Weight(Protocol):
@abstractmethod
def __add__(self: T, value: T) -> T: ... # self + value
@abstractmethod
Expand Down
14 changes: 14 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
ChangeLog
---------

**v3.4.1** (2024-12-26)

- Python 3.13 officially supported.

- The source code of this version is adapted so that it can be compiled
with MyPyC and with Cython if required. See also the notes in
requirements.dev, pyproject.toml, and setup.py.
This is not guaranteed for future versions.
(In the case of NoGraphs, however, compiling does not result in a
significant increase in speed: MyPyC can currently only store C-native
values boxed in collections, which requires frequent conversion.
And Cython only generates efficient C code here if the Python code is
specially adapted).

**v3.4.0** (2024-07-25)

- Method TraversalDepthsFirst.start_from: New parameters:
Expand Down
9 changes: 8 additions & 1 deletion docs/source/gadgets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -367,12 +367,19 @@ and calculate the manhattan distance of another vector to our position**:
>>> # Position vector multiplied by an integer value, returns Position
>>> nog.Position.at(2, 3, 4) * 3
(6, 9, 12)
>>> # Attention: Since a Position is a tuple, i * Position repeats the coordinates
>>> # Attention: For i * Position, Position behaves like a tuple
>>> # and repeats the coordinates
>>> 3 * nog.Position.at(2,3,4)
(2, 3, 4, 2, 3, 4, 2, 3, 4)
>>> # Manhattan distance of some vector
>>> nog.Position.at(2, 3, 4).manhattan_distance( (1, 1, 1) )
6
>>> # Compares equal to a tuple that contains the same coordinates
>>> nog.Position.at(2, 3, 4) == (2, 3, 4)
True
>>> # Hashable, and can be used as element in sets
>>> len(set(2 * [nog.Position.at(1,1)]))
1
When we use vector addition or subtraction to "move" some increment away from a
position, we could "leave" some coordinate ranges we would like to stay in.
Expand Down
29 changes: 22 additions & 7 deletions docs/source/gears.rst
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,11 @@ Side note about the implementation:
Defining your own gear
~~~~~~~~~~~~~~~~~~~~~~

.. note::

The functionality described in this section cannot be used when NoGraphs
is compiled by MyPyC.

You can define your own gear by subclassing one of the gear classes
described in the previous section and overwriting one of more of the
factory methods.
Expand Down Expand Up @@ -291,11 +296,21 @@ id set for given vertices, by an implementation that returns an *intbitset*.
.. code-block:: python
>>> class GearBitsetAndArrayForIntVerticesAndCFloats(
... nog.GearForIntVerticesAndIDsAndCFloats
... ):
... def vertex_id_set(self, vertices):
... return intbitset(list(vertices))
>>> def is_mypyc_compiled() -> bool:
... """
... Recognize by the file extension whether MyPyC-compiled code is running.
... """
... return not __file__.endswith(".py")
>>> if is_mypyc_compiled():
... # If NoGraphs is compiled by MyPyC, skip this example
... GearBitsetAndArrayForIntVerticesAndCFloats = \
... nog.GearForIntVerticesAndIDsAndCFloats
... else:
... class GearBitsetAndArrayForIntVerticesAndCFloats(
... nog.GearForIntVerticesAndIDsAndCFloats
... ):
... def vertex_id_set(self, vertices):
... return intbitset(list(vertices))
We can use the new gear just like the predefined ones:

Expand All @@ -304,9 +319,9 @@ We can use the new gear just like the predefined ones:
>>> our_gear = GearBitsetAndArrayForIntVerticesAndCFloats()
>>> traversal = nog.TraversalBreadthFirstFlex(
... next_edges=next_edges, gear=our_gear, vertex_to_id=nog.vertex_as_id)
>>> traversal.start_from(0).go_to(1200000) #doctest:+SLOW_TEST
>>> print(traversal.start_from(0).go_to(1200000)) #doctest:+SLOW_TEST
1200000
>>> traversal.depth #doctest:+SLOW_TEST
>>> print(traversal.depth) #doctest:+SLOW_TEST
200000
Section `Comparison of NoGraphs gears <performance_gears>` shows the
Expand Down
16 changes: 10 additions & 6 deletions docs/source/traversals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -659,8 +659,10 @@ If such an event occurs, **no vertex has been entered, and**
it is therefor
**not allowed to signal to the traversal to skip the entered (!) vertex**.
If you do this anyway, the traversal intentionally won’t catch the
*StopIteration* you throw, and a *RuntimeError* will be raised
(according to `PEP 497 <//peps.python.org/pep-0479>`_).
*StopIteration* you throw, and an exception will be raised
(CPython and PyPy will raise a *RuntimeError* according to
`PEP 497 <//peps.python.org/pep-0479>`_, and with NoGraphs as extension module
compiled by MyPyC, the StopException will fall through.).

This also means, that it is always save to ignore the return value of
throwing the *StopIteration* into the generator: it can only be the entered
Expand All @@ -680,7 +682,7 @@ by the generator and the generator skips expanding the vertex.

Then, vertex *A* is reported with event *SKIPPING_START*.
This means, *A* it is not entered. Here, throwing *StopIteration* is not
accepted and a *RuntimeError* is raised.
accepted and an exception is raised.

.. code-block:: python
Expand All @@ -694,9 +696,11 @@ accepted and a *RuntimeError* is raised.
'A'
>>> next(generator), str(traversal.event)
('A', 'DFSEvent.SKIPPING_START')
>>> generator.throw(StopIteration())
Traceback (most recent call last):
RuntimeError: generator raised StopIteration
>>> try:
... generator.throw(StopIteration())
... except (RuntimeError, StopIteration):
... print("Exception caught")
Exception caught
.. _is_tree:

Expand Down
23 changes: 21 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "nographs"
version = "3.4.0"
version = "3.4.1"
authors = [
{ name="Dr. Helmut Melcher", email="[email protected]" },
]
Expand All @@ -23,6 +23,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Intended Audience :: Developers",
Expand All @@ -44,6 +45,24 @@ Changelog = "https://nographs.readthedocs.io/en/latest/changelog.html"
[tool.coverage.report]
exclude_lines =["pragma: no cover", "@overload"]

[tool.mypy]
# Specified the target platform details in config, so the developers are
# free to run mypy on Windows, Linux, or macOS and get consistent
# results.
python_version = "3.9"
mypy_path = "src/nographs tests"
strict = true
disallow_untyped_defs = true
warn_unreachable = true
implicit_reexport = true
show_error_codes = true
show_column_numbers = true

[build-system]
requires = ["setuptools>=61.0"]
requires = [
"setuptools>=61.0",
"wheel ~=0.37.1",
# "cython", # Only needed if Cython-compiled binary wheel is demanded
# "mypy[mypyc]", # Only needed if MyPyC-compiled binary wheel is demanded
]
build-backend = "setuptools.build_meta"
8 changes: 7 additions & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ coverage>=6.3
flake8
flake8-bugbear
mypy
# -- For building a package with C-extensions compiled by MyPyC (optional)--
# mypy[mypyc]
# mypy-extensions
# -- For building a package with C-extensions compiled by Cython (optional) --
# cython
# -- source consistency checking --
pymacros4py>=0.8.2 ; python_version >= "3.10"
# -- additional packages for tests using them --
mpmath
intbitset ; python_version < "3.13" and implementation_name == "cpython" # missing on PyPy and often on new CPython-versions
# The following is missing on PyPy and often on new CPython-versions
intbitset ; python_version < "3.13" and implementation_name == "cpython"
80 changes: 80 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# This file controls the compilation of the modules of the package to extension
# modules by using MyPyC or Cython. If this option is not needed, it can be removed.
#
# Usage: If environment variable SETUP_BUILD_EXTENSION is set to MyPyC or Cython,
# extension modules are build. And if it is set to "False", or not set at all,
# the pure Python modules are copied into the wheel.

import pathlib
import os
from setuptools import setup, find_packages


def find_sources(exclude_from_compilation):
compile_this = []
for file_path in pathlib.Path('.', 'src', 'nographs').glob('**/*.py'):
if file_path.name in exclude_from_compilation:
print('setup.py: We will skip the compilation of file', file_path)
continue
compile_this.append(str(file_path))
print('setup.py: We will compile these files:\n', compile_this, "\n")
return compile_this


compiler = os.environ.get('SETUP_BUILD_EXTENSION', "False").capitalize()
match compiler:
case "False":
print("\nsetup.py: Building sdist (tarball) / and or pure Python wheel.")
print("(Set environment variable SETUP_BUILD_EXTENSION to MyPyC or Cython "
"to compile binaries.)")
ext_modules = []

case "Mypyc":
print(f"\nsetup.py: Compiling binaries using MyPyC.")
print("(Set environment variable SETUP_BUILD_EXTENSION to False "
"to build sdist / and or pure Python wheel instead.)")

from mypyc.build import mypycify
exclude_from_compilation = [
# The following file contains classes, compilation would be useful, but
# it is intentionally not compiled here due to the following issue of
# MyPyC:
# https://github.com/mypyc/mypyc/issues/1022
'depth_first_enum_types.py',
# The following file subclasses tuple[int]. MyPyC does not support this.
# But on CPython this us much faster than to store the tuple in an attribute
# Conditional class definition is also not supported. So, we simply exclude
# this file from compilation.
'_extra_matrix_gadgets.py',
]
compile_this = find_sources(exclude_from_compilation)
ext_modules = mypycify(compile_this, strip_asserts=False)

case "Cython":
print(f"\nsetup.py: Compiling binaries using Cython.")
print("(Set environment variable SETUP_BUILD_EXTENSION to False "
"to build sdist / and or pure Python wheel instead.)")
from Cython.Build import cythonize
exclude_from_compilation = []
compile_this = find_sources(exclude_from_compilation)
ext_modules = cythonize(compile_this, compiler_directives={'language_level': 3})

case _:
raise RuntimeError(
"Valid values or environment variable SETUP_BUILD_EXTENSION are:"
" 'False', 'MyPyC', and 'Cython'"
"If no value is set, this equals to 'False'.")

if ext_modules:
setup(
name='nographs',
package_dir={'': 'src'},
packages=find_packages('src'),
ext_modules=ext_modules,
)
else:
setup(
name='nographs',
package_dir={'': 'src'},
packages=find_packages('src'),
)
32 changes: 27 additions & 5 deletions src/nographs/_compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,48 @@
# --- Solve 3.9 compatibility issue


def _pairwise(iterable: Iterable[T]) -> Iterator[tuple[T, T]]:
def _manual_pairwise(iterable: Iterable[T]) -> Iterator[tuple[T, T]]:
"""Returns an iterator of paired items, overlapping, from the original.
On Python 3.10 and above, this is replaced by an alias for
:func:`itertools.pairwise`.
>>> list(_pairwise("abc"))
>>> list(_manual_pairwise("abc"))
[('a', 'b'), ('b', 'c')]
"""
a, b = itertools.tee(iterable)
next(b, None)
yield from zip(a, b)


# Under Python 3.9, detect that itertools.pairwise is missing, and replace it by a
# manual implementation. Under Python >3.9, use build-in function.
try:
from itertools import pairwise as itertools_pairwise # type: ignore[attr-defined]
# Under 3.9, MyPy need to ignore that pairwise is missing. Under >3.9, it needs
# to ignore, that the ignore statement is not needed.
from itertools import ( # type: ignore[attr-defined,unused-ignore]
pairwise as itertools_pairwise,
)
except ImportError: # pragma: no cover # not executed under Python >=3.10
pairwise = _pairwise
pairwise = _manual_pairwise
else: # pragma: no cover # not executed under Python <3.10

# We cannot assign itertools_pairwise (type: type[pairwise[Any]] to
# pairwise (type "Callable[[Iterable[T]], Iterator[tuple[T, T]]]", see above).
# So, we need to also manually implement a wrapper around itertools_pairwise.
def pairwise(iterable: Iterable[T]) -> Iterator[tuple[T, T]]:
yield from itertools_pairwise(iterable)

pairwise.__doc__ = _pairwise.__doc__
# The following would be nice, but with PyPyC, __doc__ is not writable.
# pairwise.__doc__ = _pairwise.__doc__


# --- MyPyC issues ---

try:
from mypy_extensions import trait
except (
ModuleNotFoundError
): # pragma: no cover # Not reachable if mypy_extensions are installed

def trait(cls: T) -> T:
return cls
17 changes: 10 additions & 7 deletions src/nographs/_extra_edge_gadgets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

import collections
import itertools
from collections.abc import Sequence, Mapping, Callable, Iterable
Expand Down Expand Up @@ -55,8 +53,11 @@ def adapt_edge_index(


def adapt_edge_index(
index: Union[Mapping, Sequence], *, add_inverted: bool, attributes: bool
) -> Callable:
index: Union[Mapping[Any, Any], Sequence[Any]],
*,
add_inverted: bool,
attributes: bool,
) -> Callable[[Any, Any], Any]:
"""
Read a graph from a Mapping (e.g. a Dict) or from a Sequence (e.g. a tuple
or list, if integers are used as the vertices) and provide a neighbor function
Expand Down Expand Up @@ -164,8 +165,8 @@ def adapt_edge_iterable(


def adapt_edge_iterable(
edges: Iterable[Sequence], *, add_inverted: bool, attributes: bool
) -> Callable:
edges: Iterable[Sequence[Any]], *, add_inverted: bool, attributes: bool
) -> Callable[[Any, Any], Any]:
"""
Read a graph from an Iterable of edges and provide a neighbor function
(`NextVertices` or `NextEdges`) from that data. Typically only used for test
Expand Down Expand Up @@ -195,7 +196,9 @@ def adapt_edge_iterable(
:return: Neighbor function that can be used as parameter for one of the traversal
algorithms. See `OutEdge <outgoing_edges>` for the case of attributes.
"""
edge_dict: dict[Any, list[Any]] = collections.defaultdict(list)
# Cython: edge_dict was declared as dict[Any, list[Any]], but this fails
# on Cython, because Cython does not accept a defaultdict as dict.
edge_dict = collections.defaultdict[Any, list[Any]](list)
if add_inverted:
if attributes:
# Labeled edges are provided and all data should be used
Expand Down
Loading

0 comments on commit f7b7aba

Please sign in to comment.