Skip to content

Commit

Permalink
Merge pull request #35 from nlesc-nano/version
Browse files Browse the repository at this point in the history
ENH: Allow `VersionInfo.from_str` to accept any PEP 440-compatible version string
  • Loading branch information
BvB93 authored Dec 13, 2021
2 parents 32e3a9d + 7906796 commit d8549bd
Show file tree
Hide file tree
Showing 15 changed files with 115 additions and 51 deletions.
22 changes: 11 additions & 11 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
version: [3.7, 3.8, 3.9]
version: ['3.7', '3.8', '3.9', '3.10']
special: ['']
include:
- os: ubuntu-latest
special: '; pre-release'
version: 3.9
- os: ubuntu-latest
special: '; no-optional'
version: 3.9
version: '3.10'
- os: ubuntu-latest
special: '; no-optional'
version: '3.10'
Expand Down Expand Up @@ -67,9 +64,9 @@ jobs:
run: |
case "${{ matrix.special }}" in
"; no-optional")
pytest --mypy ;;
pytest ;;
*)
pytest --mypy --doctest-modules ;;
pytest --doctest-modules ;;
esac
- name: Run codecov
Expand All @@ -89,7 +86,9 @@ jobs:
python-version: 3.9

- name: Install linters
run: pip install flake8 pydocstyle>=5.0.0
run: |
pip install flake8 pydocstyle>=5.0.0
pip install mypy types-PyYAML "assertionlib>=3.2.1" numpy
- name: Python info
run: |
Expand All @@ -101,8 +100,9 @@ jobs:

- name: Run flake8
run: flake8 nanoutils docs tests
continue-on-error: true

- name: Run pydocstyle
run: pydocstyle nanoutils docs tests
continue-on-error: true
run: pydocstyle nanoutils

- name: Run mypy
run: mypy nanoutils
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ All notable changes to this project will be documented in this file.
This project adheres to `Semantic Versioning <http://semver.org/>`_.


2.3.1
*****
* Allow ``VersionInfo.from_str`` to take any pep 440-compatible version string.
* Add ``VersionInfo.bug`` and ``VersionInfo.maintenance`` as aliases for ``VersionInfo.micro``.


2.3.0
*****
* Added ``UserMapping`` entry points for the IPython key completioner
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@


################
Nano-Utils 2.3.0
Nano-Utils 2.3.1
################
Utility functions used throughout the various nlesc-nano repositories.

Expand Down
7 changes: 6 additions & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[mypy]
ignore_missing_imports = True
warn_unused_ignores = True
warn_redundant_casts = True
warn_return_any = True
show_error_codes = True

[mypy-IPython.*]
ignore_missing_imports = True

[mypy-h5py.*]
ignore_missing_imports = True
13 changes: 10 additions & 3 deletions nanoutils/_dtype_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def _repr_helper(self: DTypeMapping, dtype_repr: Callable[[np.dtype[Any]], str])
return f"{cls.__name__}(\n{values}\n)"


class DTypeMapping(UserMapping[str, dtype]):
class DTypeMapping(UserMapping[str, "dtype[Any]"]):
"""A mapping for creating structured dtypes.
Examples
Expand Down Expand Up @@ -249,7 +249,10 @@ def __ror__(self: _ST1, other: Mapping[str, npt.DTypeLike]) -> _ST1:
return cls._reconstruct({k: np.dtype(v) for k, v in other.items()} | self._dict)


class MutableDTypeMapping(DTypeMapping, MutableUserMapping[str, dtype]): # type: ignore[misc]
class MutableDTypeMapping( # type: ignore[misc]
DTypeMapping,
MutableUserMapping[str, "dtype[Any]"],
):
"""A mutable mapping for creating structured dtypes.
Examples
Expand Down Expand Up @@ -312,7 +315,11 @@ def __delattr__(self, name: str) -> None:
return object.__delattr__(self, name)

@positional_only
def update(self, __iterable: None | _DictLike = None, **fields: npt.DTypeLike) -> None:
def update(
self,
__iterable: None | _DictLike[str, npt.DTypeLike] = None,
**fields: npt.DTypeLike,
) -> None:
"""Update the mapping from the passed mapping or iterable."""
if __iterable is None:
pass
Expand Down
4 changes: 2 additions & 2 deletions nanoutils/_lazy_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ def __init__(self) -> None:
super().__init__()
self.maxdict = 2

def repr_mappingproxy(self, x, level):
return self.repr_dict(x, level)
def repr_mappingproxy(self, x: types.MappingProxyType[Any, Any], level: int) -> str:
return self.repr_dict(x, level) # type: ignore[arg-type]


_repr = _CustomRepr().repr
Expand Down
2 changes: 1 addition & 1 deletion nanoutils/_seq_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def count(self, *args, **kwargs):
"""Return the number of times **value** occurs in the instance."""
return self._seq.count(*args, **kwargs)

def __len__(self):
def __len__(self) -> int:
"""Implement :func:`len(self) <len>`."""
return len(self._seq)

Expand Down
2 changes: 1 addition & 1 deletion nanoutils/_set_attr.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

_T1 = TypeVar('_T1')
_T2 = TypeVar('_T2')
_ST = TypeVar('_ST', bound='SetAttr')
_ST = TypeVar('_ST', bound='SetAttr[Any, Any]')


class SetAttr(Generic[_T1, _T2]):
Expand Down
14 changes: 7 additions & 7 deletions nanoutils/_user_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
from .utils import positional_only
from .typing_utils import Protocol, runtime_checkable

_T = TypeVar("_T")
_ST1 = TypeVar("_ST1", bound="UserMapping[Any, Any]")
_ST2 = TypeVar("_ST2", bound="MutableUserMapping[Any, Any]")
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
_VT_co = TypeVar("_VT_co", covariant=True)

if TYPE_CHECKING:
from IPython.lib.pretty import RepresentationPrinter

Expand All @@ -36,13 +43,6 @@ def __call__(self, __dct: dict[_KT, _VT], *, width: int) -> str: ...

_SENTINEL = object()

_T = TypeVar("_T")
_ST1 = TypeVar("_ST1", bound="UserMapping[Any, Any]")
_ST2 = TypeVar("_ST2", bound="MutableUserMapping[Any, Any]")
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
_VT_co = TypeVar("_VT_co", covariant=True)


@runtime_checkable
class _SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):
Expand Down
2 changes: 1 addition & 1 deletion nanoutils/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def format(self, obj: object) -> str: # type: ignore
return repr(obj)

@property
def __mod__(self):
def __mod__(self) -> Callable[[object], str]: # type: ignore[override]
"""Get :meth:`Formatter.format`."""
return self.format

Expand Down
2 changes: 1 addition & 1 deletion nanoutils/testing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def delete_finally(

def decorator(func: _FT) -> _FT:
@wraps(func)
def wrapper(*args, **kwargs):
def wrapper(*args: Any, **kwargs: Any) -> Any:
try:
return func(*args, **kwargs)
finally:
Expand Down
78 changes: 64 additions & 14 deletions nanoutils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"""

# flake8: noqa: E402

from __future__ import annotations

import re
Expand Down Expand Up @@ -419,8 +421,8 @@ def decorator1(func: _FT) -> _FT:
elif isinstance(exception, BaseException):
def decorator2(func: _FT) -> _FT:
@wraps(func)
def wrapper(*args, **kwargs):
raise exception
def wrapper(*args: Any, **kwargs: Any) -> Any:
raise exception # type: ignore[misc]
return wrapper # type: ignore[return-value]
return decorator2

Expand Down Expand Up @@ -503,7 +505,41 @@ def wrapper(*args: Any, **kwargs: Any) -> None:
raise TypeError(f"{exception.__class__.__name__!r}")


_PATTERN = re.compile("([0-9]+).([0-9]+).([0-9]+)")
# See PEP 440 Apendix B
_PATTERN_STR = r"""
v?
(?:
(?:(?P<epoch>[0-9]+)!)? # epoch
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
(?P<pre> # pre-release
[-_\.]?
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
[-_\.]?
(?P<pre_n>[0-9]+)?
)?
(?P<post> # post release
(?:-(?P<post_n1>[0-9]+))
|
(?:
[-_\.]?
(?P<post_l>post|rev|r)
[-_\.]?
(?P<post_n2>[0-9]+)?
)
)?
(?P<dev> # dev release
[-_\.]?
(?P<dev_l>dev)
[-_\.]?
(?P<dev_n>[0-9]+)?
)?
)
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
"""
_PATTERN = re.compile(
r"^\s*" + _PATTERN_STR + r"\s*$",
re.VERBOSE | re.IGNORECASE,
)


class VersionInfo(NamedTuple):
Expand All @@ -525,16 +561,26 @@ class VersionInfo(NamedTuple):
major: int

#: :class:`int`: The semantic_ minor version.
minor: int
minor: int = 0

#: :class:`int`: The semantic_ micro version (a.k.a. :attr:`VersionInfo.patch`).
micro: int
#: :class:`int`: The semantic_ micro version.
micro: int = 0

@property
def patch(self) -> int:
""":class:`int`: An alias for :attr:`VersionInfo.micro`."""
return self.micro

@property
def maintenance(self) -> int:
""":class:`int`: An alias for :attr:`VersionInfo.micro`."""
return self.micro

@property
def bug(self) -> int:
""":class:`int`: An alias for :attr:`VersionInfo.micro`."""
return self.micro

@classmethod
def from_str(
cls,
Expand All @@ -547,23 +593,27 @@ def from_str(
Parameters
----------
version : :class:`str`
A string representation of a version (*e.g.* :code:`version = "0.8.2"`).
The string should contain three ``"."`` separated integers, respectively,
representing the major, minor and micro/patch versions.
A PEP 440-compatible version string(*e.g.* :code:`version = "0.8.2"`).
Note that version representations are truncated at up to three integers.
fullmatch : :class:`bool`
Whether the version-string must consist exclusivelly of three
period-separated integers, or if a substring is also allowed.
Whether to perform a full or partial match on the passed string.
Returns
-------
:class:`nanoutils.VersionInfo`
A new VersionInfo instance.
See Also
--------
:pep:`440`
This PEP describes a scheme for identifying versions of Python software distributions,
and declaring dependencies on particular versions.
"""
match = _PATTERN.fullmatch(version) if fullmatch else _PATTERN.match(version)
if match is None:
raise ValueError(f"Failed to parse {version!r}")
return cls._make(int(i) for i in match.groups())
return cls._make(int(i) for i in match["release"].split(".")[:3])


def _get_directive(
Expand Down Expand Up @@ -797,10 +847,10 @@ def warning_filter(
:func:`warnings.filterwarnings` :
Insert a simple entry into the list of warnings filters (at the front).
"""
""" # noqa: E501
def decorator(func: _FT) -> _FT:
@functools.wraps(func)
def wrapper(*args, **kwargs):
def wrapper(*args: Any, **kwargs: Any) -> Any:
with warnings.catch_warnings():
warnings.filterwarnings(action, message, category, module, lineno, append)
ret = func(*args, **kwargs)
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[metadata]
description-file = README.rst
description_file = README.rst

[aliases]
# Define `python setup.py test`
Expand Down
3 changes: 0 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@
# Requirements for running tests
tests_no_optional_require = [
'assertionlib>=3.2.1',
'mypy>=0.900',
'pytest>=5.4.0',
'pytest-cov',
'pytest-mypy>=0.6.2',
'types-PyYAML',
]
tests_require = tests_no_optional_require + [
'schema',
Expand Down
7 changes: 3 additions & 4 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,14 @@ def test_version_info() -> None:
assertion.eq(tup2, (0, 1, 2))

assertion.eq(tup1.micro, tup1.patch)
assertion.eq(tup1.micro, tup1.bug)
assertion.eq(tup1.micro, tup1.maintenance)

assertion.assert_(VersionInfo.from_str, b'0.1.2', exception=TypeError)
assertion.assert_(VersionInfo.from_str, '0.1.2a', exception=ValueError)
assertion.assert_(VersionInfo.from_str, '0.1.2.3.4', exception=ValueError)
assertion.assert_(VersionInfo.from_str, '0.1.2bob', exception=ValueError)

assertion.isinstance(version_info, VersionInfo)

assertion.eq(VersionInfo.from_str('0.1.2a', fullmatch=False), (0, 1, 2))


def test_split_dict() -> None:
"""Test :func:`nanoutils.split_dict`."""
Expand Down

0 comments on commit d8549bd

Please sign in to comment.