diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a0a737c..3cbb31fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,18 +40,15 @@ jobs: - name: Install dependencies run: | pip install -U -r lint_requirements.txt - - name: Isort - run: | - isort atom examples tests -c - - name: Black + - name: Formatting if: always() run: | - black atom examples tests --check - - name: Flake8 + ruff format atom examples tests --check + - name: Linting if: always() run: | - flake8 atom examples tests - - name: Run Mypy + ruff atom examples tests + - name: Typing if: always() run: | mypy atom examples diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca53d388..053c7775 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,15 @@ repos: -- repo: https://github.com/pre-commit/mirrors-isort - rev: v5.10.1 - hooks: - - id: isort -- repo: https://github.com/psf/black - rev: 23.1.0 - hooks: - - id: black -- repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.1.5 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.0.0 + rev: v1.7.0 hooks: - id: mypy additional_dependencies: [] \ No newline at end of file diff --git a/README.rst b/README.rst index 9e0aad78..328e9616 100644 --- a/README.rst +++ b/README.rst @@ -10,6 +10,9 @@ Welcome to Atom .. image:: https://readthedocs.org/projects/atom/badge/?version=latest :target: https://atom.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Ruff Atom is a framework for creating memory efficient Python objects with enhanced features such as dynamic initialization, validation, and change notification for diff --git a/atom/atom.py b/atom/atom.py index 79b1ec22..adead3e8 100644 --- a/atom/atom.py +++ b/atom/atom.py @@ -77,7 +77,7 @@ def __reduce_ex__(self, proto): fully understands the rammifications. """ - args = (type(self),) + self.__getnewargs__() + args = (type(self), *self.__getnewargs__()) return (__newobj__, args, self.__getstate__()) def __getnewargs__(self): diff --git a/atom/catom.pyi b/atom/catom.pyi index 594a1f67..6243ee3a 100644 --- a/atom/catom.pyi +++ b/atom/catom.pyi @@ -28,6 +28,15 @@ from .atom import Atom from .property import Property from .typing_utils import ChangeDict +class ChangeType(IntFlag): + CREATE = ... + UPDATE = ... + DELETE = ... + EVENT = ... + PROPERTY = ... + CONTAINER = ... + ANY = ... + def reset_property(prop: Property[Any, Any], owner: Atom) -> None: ... class CAtom: @@ -556,12 +565,3 @@ class GetState(IntEnum): Property = ... MemberMethod_Object = ... ObjectMethod_Name = ... - -class ChangeType(IntFlag): - CREATE = ... - UPDATE = ... - DELETE = ... - EVENT = ... - PROPERTY = ... - CONTAINER = ... - ANY = ... diff --git a/atom/coerced.py b/atom/coerced.py index d5e9d70c..a48866cf 100644 --- a/atom/coerced.py +++ b/atom/coerced.py @@ -60,9 +60,14 @@ def __init__(self, kind, args=None, kwargs=None, *, factory=None, coercer=None): args = args or () kwargs = kwargs or {} if opt: - factory = lambda: None + + def factory(): + return None else: - factory = lambda: kind[0](*args, **kwargs) + + def factory(): + return kind[0](*args, **kwargs) + self.set_default_value_mode(DefaultValue.CallObject, factory) if not coercer and (isinstance(origin, tuple) or len(temp) > 1): diff --git a/atom/dict.pyi b/atom/dict.pyi index 80c6fd17..675f53b8 100644 --- a/atom/dict.pyi +++ b/atom/dict.pyi @@ -1,11 +1,21 @@ # -------------------------------------------------------------------------------------- -# Copyright (c) 2021, Nucleic Development Team. +# Copyright (c) 2021-2023, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -from typing import Any, Dict as TDict, Optional, Tuple, Type, TypeVar, overload +from typing import ( + Any, + Callable, + DefaultDict as TDefaultDict, + Dict as TDict, + Optional, + Tuple, + Type, + TypeVar, + overload, +) from .catom import Member @@ -357,4 +367,365 @@ class Dict(Member[TDict[KT, VT], TDict[KT, VT]]): default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT]: ... -class DefaultDict(Dict[KT, VT]): ... +class DefaultDict(Member[TDefaultDict[KT, VT], TDefaultDict[KT, VT]]): + # Untyped + @overload + def __new__( + cls, + key: None = None, + value: None = None, + default: Optional[TDict[Any, Any]] = None, + *, + missing: None = None, + ) -> DefaultDict[Any, Any]: ... + # Typed by missing + @overload + def __new__( + cls, + key: None = None, + value: None = None, + default: Optional[TDict[Any, Any]] = None, + *, + missing: Callable[[], VT], + ) -> DefaultDict[Any, VT]: ... + # Typed by defaultdict default value + @overload + def __new__( + cls, + key: None = None, + value: None = None, + *, + default: TDefaultDict[Any, VT], + missing: Callable[[], VT], + ) -> DefaultDict[Any, VT]: ... + # No default + # Typed keys + # - type + @overload + def __new__( + cls, + key: Type[KT], + value: None = None, + default: Optional[TDict[Any, Any]] = None, + *, + missing: None = None, + ) -> DefaultDict[KT, Any]: ... + # - 1-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT]], + value: None = None, + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, Any]: ... + # - 2-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1]], + value: None = None, + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1, Any]: ... + # - 3-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1], Type[KT2]], + value: None = None, + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1 | KT2, Any]: ... + # - member + @overload + def __new__( + cls, + key: Member[KT, Any], + value: None = None, + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, Any]: ... + # Typed values + # - type + @overload + def __new__( + cls, key: None, value: Type[VT], default: Optional[TDict[Any, Any]] = None + ) -> DefaultDict[Any, VT]: ... + # - 1-tuple + @overload + def __new__( + cls, + key: None, + value: Tuple[Type[VT]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[Any, VT]: ... + # - 2-tuple + @overload + def __new__( + cls, + key: None, + value: Tuple[Type[VT], Type[VT1]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[Any, VT | VT1]: ... + # - 3-tuple + @overload + def __new__( + cls, + key: None, + value: Tuple[Type[VT], Type[VT1], Type[VT2]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[Any, VT | VT1 | VT2]: ... + # - member + @overload + def __new__( + cls, + key: None, + value: Member[VT, Any], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[Any, VT]: ... + # Typed value through keyword + # - type + @overload + def __new__( + cls, + key: None = None, + *, + value: Type[VT], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[Any, VT]: ... + # - 1-tuple + @overload + def __new__( + cls, + key: None = None, + *, + value: Tuple[Type[VT]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[Any, VT]: ... + # - 2-tuple + @overload + def __new__( + cls, + key: None = None, + *, + value: Tuple[Type[VT], Type[VT1]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[Any, VT | VT1]: ... + # - 3-tuple + @overload + def __new__( + cls, + key: None = None, + *, + value: Tuple[Type[VT], Type[VT1], Type[VT2]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[Any, VT | VT1 | VT2]: ... + # - member + @overload + def __new__( + cls, + key: None = None, + *, + value: Member[VT, Any], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[Any, VT]: ... + # Typed key and value + # - value simple type + # - key type + @overload + def __new__( + cls, key: Type[KT], value: Type[VT], default: Optional[TDict[Any, Any]] = None + ) -> DefaultDict[KT, VT]: ... + # - key 1-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT]], + value: Type[VT], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT]: ... + # - key 2-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1]], + value: Type[VT], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1, VT]: ... + # - key 3-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1], Type[KT2]], + value: Type[VT], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1 | KT2, VT]: ... + # - key member + @overload + def __new__( + cls, + key: Member[KT, Any], + value: Type[VT], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT]: ... + # - Value as single element tuple + # - key type + @overload + def __new__( + cls, + key: Type[KT], + value: Tuple[Type[VT]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT]: ... + # - key 1-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT]], + value: Tuple[Type[VT]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT]: ... + # - key 2-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1]], + value: Tuple[Type[VT]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1, VT]: ... + # - key 3-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1], Type[KT2]], + value: Tuple[Type[VT]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1 | KT2, VT]: ... + # - key member + @overload + def __new__( + cls, + key: Member[KT, Any], + value: Tuple[Type[VT]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT]: ... + # - Value as 2-tuple + # - key type + @overload + def __new__( + cls, + key: Type[KT], + value: Tuple[Type[VT], Type[VT1]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT | VT1]: ... + # - key 1-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT]], + value: Tuple[Type[VT], Type[VT1]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT | VT1]: ... + # - key 2-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1]], + value: Tuple[Type[VT], Type[VT1]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1, VT | VT1]: ... + # - key 3-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1], Type[KT2]], + value: Tuple[Type[VT], Type[VT1]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1 | KT2, VT | VT1]: ... + # - key member + @overload + def __new__( + cls, + key: Member[KT, Any], + value: Tuple[Type[VT], Type[VT1]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT | VT1]: ... + # - Value as 3-tuple + # - key type + @overload + def __new__( + cls, + key: Type[KT], + value: Tuple[Type[VT], Type[VT1], Type[VT2]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT | VT1 | VT2]: ... + # - key 1-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT]], + value: Tuple[Type[VT], Type[VT1], Type[VT2]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT | VT1 | VT2]: ... + # - key 2-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1]], + value: Tuple[Type[VT], Type[VT1], Type[VT2]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1, VT | VT1 | VT2]: ... + # - key 3-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1], Type[KT2]], + value: Tuple[Type[VT], Type[VT1], Type[VT2]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1 | KT2, VT | VT1 | VT2]: ... + # - key member + @overload + def __new__( + cls, + key: Member[KT, Any], + value: Tuple[Type[VT], Type[VT1], Type[VT2]], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT | VT1 | VT2]: ... + # - value as member + # - key type + @overload + def __new__( + cls, + key: Type[KT], + value: Member[VT, Any], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT]: ... + # - key 1-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT]], + value: Member[VT, Any], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT]: ... + # - key 2-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1]], + value: Member[VT, Any], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1, VT]: ... + # - key 3-tuple + @overload + def __new__( + cls, + key: Tuple[Type[KT], Type[KT1], Type[KT2]], + value: Member[VT, VT], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT | KT1 | KT2, VT]: ... + # - key member + @overload + def __new__( + cls, + key: Member[KT, KT], + value: Member[VT, VT], + default: Optional[TDict[Any, Any]] = None, + ) -> DefaultDict[KT, VT]: ... diff --git a/atom/instance.py b/atom/instance.py index 10465454..60e298fb 100644 --- a/atom/instance.py +++ b/atom/instance.py @@ -69,7 +69,10 @@ def __init__(self, kind, args=None, kwargs=None, *, factory=None, optional=None) elif args is not None or kwargs is not None: args = args or () kwargs = kwargs or {} - factory = lambda: kind[0](*args, **kwargs) + + def factory(): + return kind[0](*args, **kwargs) + self.set_default_value_mode(DefaultValue.CallObject, factory) elif optional is False: self.set_default_value_mode(DefaultValue.NonOptional, None) @@ -160,7 +163,10 @@ def default(self, owner): kind = self.resolve() args = self.args or () kwargs = self.kwargs or {} - factory = lambda: kind(*args, **kwargs) + + def factory(): + return kind(*args, **kwargs) + self.set_default_value_mode(DefaultValue.CallObject, factory) return kind(*args, **kwargs) diff --git a/atom/meta/atom_meta.py b/atom/meta/atom_meta.py index 83f798b7..05cbd08e 100644 --- a/atom/meta/atom_meta.py +++ b/atom/meta/atom_meta.py @@ -517,7 +517,7 @@ class so that the CAtom class can allocate exactly enough space for __atom_members__: Mapping[str, Member] __atom_specific_members__: FrozenSet[str] - def __new__( # noqa: C901 + def __new__( meta, name: str, bases: Tuple[type, ...], diff --git a/atom/typed.py b/atom/typed.py index 0f772d38..2b16d39a 100644 --- a/atom/typed.py +++ b/atom/typed.py @@ -69,7 +69,10 @@ def __init__(self, kind, args=None, kwargs=None, *, factory=None, optional=None) elif args is not None or kwargs is not None: args = args or () kwargs = kwargs or {} - factory = lambda: kind(*args, **kwargs) + + def factory(): + return kind(*args, **kwargs) + self.set_default_value_mode(DefaultValue.CallObject, factory) elif optional is False: self.set_default_value_mode(DefaultValue.NonOptional, None) @@ -162,7 +165,10 @@ def default(self, owner): (kind,) = extract_types(self.resolve()) args = self.args or () kwargs = self.kwargs or {} - factory = lambda: kind(*args, **kwargs) + + def factory(): + return kind(*args, **kwargs) + self.set_default_value_mode(DefaultValue.CallObject, factory) return kind(*args, **kwargs) diff --git a/examples/api/composition.py b/examples/api/composition.py index b1d228bd..8fe7c1dc 100644 --- a/examples/api/composition.py +++ b/examples/api/composition.py @@ -38,7 +38,7 @@ class Person(Atom): # uses kwargs provided in the definition # When the member is provided a way to build a default value, it assumes it # is not optional by default, i.e. None is not a valid value. - fluffy = Typed(Dog, kwargs=dict(name="Fluffy")) + fluffy = Typed(Dog, kwargs={"name": "Fluffy"}) # uses an object provided in Person constructor new_dog = Typed(Dog) diff --git a/examples/api/containers.py b/examples/api/containers.py index 1ce94652..a79bd32a 100644 --- a/examples/api/containers.py +++ b/examples/api/containers.py @@ -18,7 +18,7 @@ class Data(Atom): dtuple = Tuple(default=(5, 4, 3)) - ddict = Dict(default=dict(foo=1, bar="a")) + ddict = Dict(default={"foo": 1, "bar": "a"}) def _observe_dcont_list(self, change): print("container list change: {0}".format(change["value"])) diff --git a/lint_requirements.txt b/lint_requirements.txt index 517c4f72..e3b95ac9 100644 --- a/lint_requirements.txt +++ b/lint_requirements.txt @@ -1,4 +1,2 @@ -black -flake8 -isort +ruff mypy \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d46a6e54..ca60a6df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,18 +7,14 @@ # -------------------------------------------------------------------------------------- [project] -name = "atom" -description = "Memory efficient Python objects" -readme = "README.rst" -requires-python = ">=3.8" -license = {file = "LICENSE"} -authors = [ - {name = "The Nucleic Development Team", email = "sccolbert@gmail.com"} -] -maintainers = [ - {name = "Matthieu C. Dartiailh", email = "m.dartiailh@gmail.com"} -] -classifiers = [ + name = "atom" + description = "Memory efficient Python objects" + readme = "README.rst" + requires-python = ">=3.8" + license = { file = "LICENSE" } + authors = [{ name = "The Nucleic Development Team", email = "sccolbert@gmail.com" }] + maintainers = [{ name = "Matthieu C. Dartiailh", email = "m.dartiailh@gmail.com" }] + classifiers = [ "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3", @@ -27,29 +23,27 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", -] -dependencies = [ - "typing_extensions;python_version<'3.11'" -] -dynamic=["version"] + ] + dependencies = ["typing_extensions;python_version<'3.11'"] + dynamic = ["version"] -[project.urls] -homepage = "https://github.com/nucleic/atom" -documentation = "https://atom.readthedocs.io/en/latest/" -repository = "https://github.com/nucleic/atom" -changelog = "https://github.com/nucleic/atom/blob/main/releasenotes.rst" + [project.urls] + homepage = "https://github.com/nucleic/atom" + documentation = "https://atom.readthedocs.io/en/latest/" + repository = "https://github.com/nucleic/atom" + changelog = "https://github.com/nucleic/atom/blob/main/releasenotes.rst" [build-system] -requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3", "cppy>=1.2.0"] -build-backend = "setuptools.build_meta" + requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3", "cppy>=1.2.0"] + build-backend = "setuptools.build_meta" [tool.setuptools] -include-package-data = false -package-data = {atom = ["py.typed", "*.pyi"]} + include-package-data = false + package-data = { atom = ["py.typed", "*.pyi"] } [tool.setuptools_scm] -write_to = "atom/version.py" -write_to_template = """ + write_to = "atom/version.py" + write_to_template = """ # -------------------------------------------------------------------------------------- # Copyright (c) 2013-2023, Nucleic Development Team. # @@ -78,20 +72,23 @@ del namedtuple, _version_info, parts __version__ = "{version}" """ -[tool.black] -line-length = 88 # Enforce the default value +[tool.ruff] + src = ["src"] + select = ["C", "E", "F", "W", "I", "C90", "RUF"] + extend-exclude = ["tests/instruments/hardware/nifpga/scope_based"] + extend-ignore = ["E501", "RUF012"] + line-length = 88 -[tool.pytest.ini_options] -minversion = "6.0" + [tool.ruff.isort] + combine-as-imports = true + known-first-party = ["atom"] + + [tool.ruff.mccabe] + max-complexity = 20 [tool.mypy] -follow_imports = "normal" -strict_optional = true + follow_imports = "normal" + strict_optional = true -[tool.isort] -multi_line_output = 3 -include_trailing_comma = true -combine_as_imports = true -force_grid_wrap = 0 -use_parentheses = true -line_length = 88 +[tool.pytest.ini_options] + minversion = "6.0" diff --git a/tests/datastructure/test_sortedmap.py b/tests/datastructure/test_sortedmap.py index eccc8087..ee3e230a 100644 --- a/tests/datastructure/test_sortedmap.py +++ b/tests/datastructure/test_sortedmap.py @@ -131,24 +131,24 @@ def test_iter(smap): def test_ordering_with_inhomogeneous(smap): """Test the ordering of the map.""" smap["d"] = 4 - assert [k for k in smap.keys()] == ["a", "b", "c", "d"] + assert list(smap.keys()) == ["a", "b", "c", "d"] smap["0"] = 4 - assert [k for k in smap.keys()] == ["0", "a", "b", "c", "d"] + assert list(smap.keys()) == ["0", "a", "b", "c", "d"] smap[1] = 4 - assert [k for k in smap.keys()] == [1, "0", "a", "b", "c", "d"] + assert list(smap.keys()) == [1, "0", "a", "b", "c", "d"] # Test ordering None, which is smaller than anything s = sortedmap() s[1] = 1 s[None] = 1 - assert [k for k in s.keys()] == [None, 1] + assert list(s.keys()) == [None, 1] s = sortedmap() s[None] = 1 s[1] = 1 - assert [k for k in s.keys()] == [None, 1] + assert list(s.keys()) == [None, 1] # Test ordering class that cannot be ordered through the usual mean class T: @@ -165,13 +165,9 @@ class T: s = sortedmap() s[t1] = 1 s[t2] = 1 - assert [k for k in s.keys()] == [t1, t2] if id(t1) < id(t2) else [t2, t1] + assert list(s.keys()) == [t1, t2] if id(t1) < id(t2) else [t2, t1] s[u] = 1 - assert ( - [k for k in s.keys()][0] is u - if id(T) < id(oT) - else [k for k in s.keys()][-1] is u - ) + assert next(iter(s.keys())) is u if id(T) < id(oT) else list(s.keys())[-1] is u def test_deleting_keys(smap): diff --git a/tests/test_atom.py b/tests/test_atom.py index 956c9736..a22377c6 100644 --- a/tests/test_atom.py +++ b/tests/test_atom.py @@ -271,7 +271,7 @@ class MyAtom(Atom): val = Value() a = MyAtom() - l1 = list() + l1 = [] a.val = l1 a.val.append(a) diff --git a/tests/test_atom_from_annotations.py b/tests/test_atom_from_annotations.py index 7cc33e42..31f9773e 100644 --- a/tests/test_atom_from_annotations.py +++ b/tests/test_atom_from_annotations.py @@ -303,10 +303,10 @@ class A(Atom, use_annotations=True): def test_annotations_no_default_for_instance(): with pytest.raises(ValueError): - class A(Atom, use_annotations=True): # noqa + class A(Atom, use_annotations=True): a: Iterable = [] with pytest.raises(ValueError): - class B(Atom, use_annotations=True): # noqa + class B(Atom, use_annotations=True): a: Optional[Iterable] = [] diff --git a/tests/test_atomlist.py b/tests/test_atomlist.py index 84b4cf35..4667a4e1 100644 --- a/tests/test_atomlist.py +++ b/tests/test_atomlist.py @@ -140,7 +140,7 @@ def test_untyped_convert_to_list(self): def test_untyped_iterate(self): self.model.untyped = list(range(10)) - data = [i for i in self.model.untyped] + data = list(self.model.untyped) assert data == list(range(10)) def test_untyped_copy_on_assign(self): @@ -160,7 +160,7 @@ def test_untyped_extend(self): def test_untyped_insert(self): self.model.untyped = list(range(10)) self.model.untyped.insert(0, 19) - assert self.model.untyped == [19] + list(range(10)) + assert self.model.untyped == [19, *list(range(10))] def test_untyped_remove(self): self.model.untyped = list(range(10)) @@ -267,7 +267,7 @@ def test_typed_convert_to_list(self): def test_typed_iterate(self): self.model.typed = list(range(10)) - data = [i for i in self.model.typed] + data = list(self.model.typed) assert data == list(range(10)) def test_typed_copy_on_assign(self): @@ -287,7 +287,7 @@ def test_typed_extend(self): def test_typed_insert(self): self.model.typed = list(range(10)) self.model.typed.insert(0, 19) - assert self.model.typed == [19] + list(range(10)) + assert self.model.typed == [19, *list(range(10))] def test_typed_remove(self): self.model.typed = list(range(10)) @@ -554,7 +554,10 @@ def test_container_sort(container_model, kind): @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_key_sort(container_model, kind): mlist = getattr(container_model, kind) - key = lambda i: i + + def key(i): + return i + mlist.sort(key=key, reverse=True) verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): diff --git a/tests/test_atomset.py b/tests/test_atomset.py index bf21778d..c08cb758 100644 --- a/tests/test_atomset.py +++ b/tests/test_atomset.py @@ -42,7 +42,7 @@ def test_repr(atom_set, member): """Test the repr.""" s = getattr(atom_set.__class__, member).default_value_mode[1] if not s: - s = {i for i in range(10)} + s = set(range(10)) setattr(atom_set, member, s) assert repr(s) in repr(getattr(atom_set, member)) @@ -52,7 +52,7 @@ def test_len(atom_set, member): """Test the len.""" s = getattr(atom_set.__class__, member).default_value_mode[1] if not s: - s = {i for i in range(10)} + s = set(range(10)) setattr(atom_set, member, s) assert len(getattr(atom_set, member)) == len(s) @@ -60,7 +60,7 @@ def test_len(atom_set, member): @pytest.mark.parametrize("member", MEMBERS) def test_contains(atom_set, member): """Test __contains__.""" - s = {i for i in range(10)} + s = set(range(10)) setattr(atom_set, member, s) assert 5 in getattr(atom_set, member) diff --git a/tests/test_default_values.py b/tests/test_default_values.py index 38a5f81a..29fa0e34 100644 --- a/tests/test_default_values.py +++ b/tests/test_default_values.py @@ -16,7 +16,7 @@ call_object_object_handler: advanced used case not used internally call_object_object_name_handler: advanced used case not used internally object_method_handler - object_method_name_handler: uadvanced used case not used internally + object_method_name_handler: advanced used case not used internally member_method_object_handler """ @@ -144,7 +144,7 @@ class DictTest(Atom): default = Dict(default={"a": 1}) assert DictTest.no_default.default_value_mode[0] == DefaultValue.Dict - assert DictTest().no_default == dict() + assert DictTest().no_default == {} assert DictTest.default.default_value_mode[0] == DefaultValue.Dict default_value = DictTest.default.default_value_mode[1] @@ -171,18 +171,18 @@ class SetTest(Atom): @pytest.mark.parametrize( "member, expected, mode", [ - (Typed(int, ("101",), dict(base=2)), 5, DefaultValue.CallObject), + (Typed(int, ("101",), {"base": 2}), 5, DefaultValue.CallObject), (Typed(int, factory=lambda: int(5)), 5, DefaultValue.CallObject), ( - ForwardTyped(lambda: int, ("101",), dict(base=2)), + ForwardTyped(lambda: int, ("101",), {"base": 2}), 5, DefaultValue.MemberMethod_Object, ), (ForwardTyped(lambda: int, factory=lambda: int(5)), 5, DefaultValue.CallObject), - (Instance(int, ("101",), dict(base=2)), 5, DefaultValue.CallObject), + (Instance(int, ("101",), {"base": 2}), 5, DefaultValue.CallObject), (Instance(int, factory=lambda: int(5)), 5, DefaultValue.CallObject), ( - ForwardInstance(lambda: int, ("101",), dict(base=2)), + ForwardInstance(lambda: int, ("101",), {"base": 2}), 5, DefaultValue.MemberMethod_Object, ), @@ -192,6 +192,8 @@ class SetTest(Atom): DefaultValue.CallObject, ), (Value(factory=lambda: 5), 5, DefaultValue.CallObject), + (Coerced((int, type(None)), coercer=int), None, DefaultValue.CallObject), + (Coerced(int, ()), 0, DefaultValue.CallObject), (Coerced(int, factory=lambda: 5), 5, DefaultValue.CallObject), ], ) @@ -203,6 +205,8 @@ class CallTest(Atom): assert CallTest.m.default_value_mode[0] == mode assert CallTest().m == expected + # Called twice to call the resolved version of the default for forward members + assert CallTest().m == expected assert CallTest.m.default_value_mode[0] == DefaultValue.CallObject diff --git a/tests/test_delegator.py b/tests/test_delegator.py index fd851e49..3be09a22 100644 --- a/tests/test_delegator.py +++ b/tests/test_delegator.py @@ -180,7 +180,9 @@ class DelegateTest(Atom): assert DelegateTest.d.index == new_index assert DelegateTest.d.delegate.index == new_index - observer = lambda s, c: None + def observer(s, c): + return None + assert not DelegateTest.d.delegate.static_observers() DelegateTest.d.add_static_observer(observer) assert DelegateTest.d.delegate.static_observers() diff --git a/tests/test_examples.py b/tests/test_examples.py index 603be619..cd14bffe 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -13,7 +13,7 @@ import pytest example_folder = os.path.join(os.path.dirname(__file__), "..", "examples") -examples = list() +examples = [] for dirpath, dirnames, filenames in os.walk(example_folder): examples += [os.path.join(dirpath, f) for f in filenames] diff --git a/tests/test_mem.py b/tests/test_mem.py index 599e0b20..61db9068 100644 --- a/tests/test_mem.py +++ b/tests/test_mem.py @@ -6,6 +6,8 @@ # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- import gc +import os +import sys import time from multiprocessing import Process @@ -66,11 +68,15 @@ def atomreftest(cls): obj = cls() obj.data while True: - ref = atomref(obj) # noqa + ref = atomref(obj) del ref gc.collect() +@pytest.mark.skipif( + "CI" in os.environ and sys.platform.startswith("darwin"), + reason="Flaky on MacOS CI runners", +) @pytest.mark.skipif(PSUTIL_UNAVAILABLE, reason="psutil is not installed") @pytest.mark.parametrize("label", MEM_TESTS.keys()) def test_mem_usage(label): diff --git a/tests/test_member.py b/tests/test_member.py index 809979cd..6944b7b1 100644 --- a/tests/test_member.py +++ b/tests/test_member.py @@ -159,8 +159,8 @@ class MetadataTest(Atom): mt = MetadataTest() m = mt.get_member("m") assert m.metadata == {"pref": True} - m.metadata = dict(a=1, b=2) - assert m.metadata == dict(a=1, b=2) + m.metadata = {"a": 1, "b": 2} + assert m.metadata == {"a": 1, "b": 2} m.metadata = None assert m.metadata is None diff --git a/tests/test_observe.py b/tests/test_observe.py index 1ee26696..ad4881b4 100644 --- a/tests/test_observe.py +++ b/tests/test_observe.py @@ -749,12 +749,12 @@ def test_manually_notifying(sd_observed_atom): assert nt.count == 1 # Check only dynamic notifiers are called - nt.notify("val", dict(name="val")) + nt.notify("val", {"name": "val"}) assert ob.count == 2 assert nt.count == 1 # Check that only static notifiers are called - type(nt).val.notify(nt, dict()) + type(nt).val.notify(nt, {}) assert ob.count == 2 assert nt.count == 2 diff --git a/tests/type_checking/test_property.yml b/tests/type_checking/test_property.yml index 23a6b945..d25771f9 100644 --- a/tests/type_checking/test_property.yml +++ b/tests/type_checking/test_property.yml @@ -12,8 +12,8 @@ class A(Atom): m = Property() - reveal_type(A.m) # N: Revealed type is "atom.property.Property[, ]" - reveal_type(A().m) # N: Revealed type is "" + reveal_type(A.m) # N: Revealed type is "atom.property.Property[Never, Never]" + reveal_type(A().m) # N: Revealed type is "Never" - case: property-no-setter @@ -26,7 +26,7 @@ class A(Atom): m = Property(g) - reveal_type(A.m) # N: Revealed type is "atom.property.Property[builtins.int, ]" + reveal_type(A.m) # N: Revealed type is "atom.property.Property[builtins.int, Never]" reveal_type(A().m) # N: Revealed type is "builtins.int" - case: property @@ -70,5 +70,5 @@ def m(self) -> int: return 1 - reveal_type(A.m) # N: Revealed type is "atom.property.Property[builtins.int, ]" + reveal_type(A.m) # N: Revealed type is "atom.property.Property[builtins.int, Never]" reveal_type(A().m) # N: Revealed type is "builtins.int" \ No newline at end of file