diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 44524c99..00000000 --- a/.coveragerc +++ /dev/null @@ -1,19 +0,0 @@ -[run] -branch = True -include = - */atom/* -omit = - */tests/* - -[report] -# Regexes for lines to exclude from consideration -exclude_lines = - # Have to re-enable the standard pragma - pragma: no cover - - # Don't complain if tests don't hit defensive assertion code: - raise NotImplementedError - pass - - # Dont't complain about type checking - if TYPE_CHECKING: diff --git a/.flake8 b/.flake8 deleted file mode 100644 index a1cd36c3..00000000 --- a/.flake8 +++ /dev/null @@ -1,13 +0,0 @@ -[flake8] -exclude = - .git, - __pycache__, - docs/source/conf.py, - build, - dist, -ignore = E203, E266, E501, W503, E731 -# line length is intentionally set to 80 here because atom uses Bugbear -# See https://github.com/psf/black/blob/master/README.md#line-length for more details -max-line-length = 80 -max-complexity = 19 -select = B,C,E,F,W,T4,B9 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4df6c460..32cad389 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,8 +105,6 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - cache: 'pip' - cache-dependency-path: 'test_requirements.txt' - name: Install dependencies run: | python -m pip install --upgrade pip @@ -119,11 +117,9 @@ jobs: run: | pip install -e . - name: Test with pytest - # XXX Disabled warnings check ( -W error) to be able to test on 3.11 - # (pyparsing deprecation) run: | pip install -r test_requirements.txt - python -X dev -m pytest tests --ignore=tests/type_checking --cov --cov-report xml -v + python -X dev -m pytest tests --ignore=tests/type_checking --cov --cov-report xml -v -W error - name: Generate C++ coverage reports if: (github.event_name != 'schedule' && matrix.os != 'windows-latest') run: | diff --git a/atom/api.py b/atom/api.py index d86944b5..ab193a20 100644 --- a/atom/api.py +++ b/atom/api.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Module exporting the public interface to atom. +"""Module exporting the public interface to atom.""" -""" from .atom import Atom from .catom import ( CAtom, @@ -61,7 +60,7 @@ from .set import Set from .signal import Signal from .subclass import ForwardSubclass, Subclass -from .tuple import Tuple +from .tuple import FixedTuple, Tuple from .typed import ForwardTyped, Typed from .typing_utils import ChangeDict @@ -120,5 +119,6 @@ "Tuple", "ForwardTyped", "Typed", + "FixedTuple", "ChangeDict", ] diff --git a/atom/catom.pyi b/atom/catom.pyi index 6243ee3a..3c59b58c 100644 --- a/atom/catom.pyi +++ b/atom/catom.pyi @@ -537,6 +537,7 @@ class Validate(IntEnum): Dict = ... DefaultDict = ... Enum = ... + FixedTuple = ... Float = ... FloatPromote = ... FloatRange = ... diff --git a/atom/meta/__init__.py b/atom/meta/__init__.py index 0bd4e11c..6b6b1274 100644 --- a/atom/meta/__init__.py +++ b/atom/meta/__init__.py @@ -6,6 +6,7 @@ # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Atom metaclass and tools used to create atom subclasses.""" + from .atom_meta import AtomMeta, MissingMemberWarning, add_member, clone_if_needed from .member_modifiers import set_default from .observation import observe diff --git a/atom/meta/annotation_utils.py b/atom/meta/annotation_utils.py index c22fbb18..7dd36bca 100644 --- a/atom/meta/annotation_utils.py +++ b/atom/meta/annotation_utils.py @@ -16,7 +16,7 @@ from ..scalars import Bool, Bytes, Callable as ACallable, Float, Int, Str, Value from ..set import Set as ASet from ..subclass import Subclass -from ..tuple import Tuple as ATuple +from ..tuple import FixedTuple, Tuple as ATuple from ..typed import Typed from ..typing_utils import extract_types, get_args, is_optional from .member_modifiers import set_default @@ -71,10 +71,10 @@ def generate_member_from_type_or_generic( ): # We can only validate homogeneous tuple so far so we ignore other cases if t is tuple: - if (...) in parameters or len(set(parameters)) == 1: + if (...) in parameters: parameters = (parameters[0],) else: - parameters = () + m_cls = FixedTuple parameters = tuple( generate_member_from_type_or_generic( t, _NO_DEFAULT, annotate_type_containers - 1 diff --git a/atom/meta/atom_meta.py b/atom/meta/atom_meta.py index 05cbd08e..ed25e1f4 100644 --- a/atom/meta/atom_meta.py +++ b/atom/meta/atom_meta.py @@ -6,6 +6,7 @@ # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Metaclass implementing atom members customization.""" + import copyreg import warnings from types import FunctionType diff --git a/atom/meta/member_modifiers.py b/atom/meta/member_modifiers.py index ccb0509f..f3ddad1b 100644 --- a/atom/meta/member_modifiers.py +++ b/atom/meta/member_modifiers.py @@ -6,6 +6,7 @@ # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Custom marker objects used to modify the default settings of a member.""" + from typing import Any, Optional @@ -28,3 +29,9 @@ def __init__(self, value: Any) -> None: def clone(self) -> "set_default": """Create a clone of the sentinel.""" return type(self)(self.value) + + +# XXX add more sentinels here to allow customizing members without using the +# members themselves: +# - tag +# diff --git a/atom/meta/observation.py b/atom/meta/observation.py index a8ddeead..d6447403 100644 --- a/atom/meta/observation.py +++ b/atom/meta/observation.py @@ -6,6 +6,7 @@ # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Tools to declare static observers in Atom subclasses""" + from types import FunctionType from typing import ( TYPE_CHECKING, diff --git a/atom/src/behaviors.h b/atom/src/behaviors.h index 6319c0a8..0b585c51 100644 --- a/atom/src/behaviors.h +++ b/atom/src/behaviors.h @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------- -| Copyright (c) 2013-2023, Nucleic Development Team. +| Copyright (c) 2013-2024, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | @@ -132,6 +132,7 @@ enum Mode Str, StrPromote, Tuple, + FixedTuple, List, ContainerList, Set, diff --git a/atom/src/enumtypes.cpp b/atom/src/enumtypes.cpp index ffa59ace..a6106f94 100644 --- a/atom/src/enumtypes.cpp +++ b/atom/src/enumtypes.cpp @@ -267,6 +267,7 @@ bool init_enumtypes() add_long( dict_ptr, expand_enum( Str ) ); add_long( dict_ptr, expand_enum( StrPromote ) ); add_long( dict_ptr, expand_enum( Tuple ) ); + add_long( dict_ptr, expand_enum( FixedTuple ) ); add_long( dict_ptr, expand_enum( List ) ); add_long( dict_ptr, expand_enum( ContainerList ) ); add_long( dict_ptr, expand_enum( Set ) ); diff --git a/atom/src/validatebehavior.cpp b/atom/src/validatebehavior.cpp index 279380bf..3e25a765 100644 --- a/atom/src/validatebehavior.cpp +++ b/atom/src/validatebehavior.cpp @@ -67,6 +67,25 @@ Member::check_context( Validate::Mode mode, PyObject* context ) return false; } break; + case Validate::FixedTuple: + { + if( !PyTuple_Check( context ) ) + { + cppy::type_error( context, "tuple of types or Members" ); + return false; + } + Py_ssize_t len = PyTuple_GET_SIZE( context ); + for( Py_ssize_t i = 0; i < len; i++ ) + { + PyObject* t = PyTuple_GET_ITEM( context, i ); + if( !Member::TypeCheck( t ) ) + { + cppy::type_error( context, "tuple of types or Members" ); + return false; + } + } + break; + } case Validate::Dict: { if( !PyTuple_Check( context ) ) @@ -463,6 +482,56 @@ tuple_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newval } +PyObject* +fixed_tuple_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) +{ + if( !PyTuple_Check( newvalue ) ) + { + return validate_type_fail( member, atom, newvalue, "tuple" ); + } + cppy::ptr tupleptr( cppy::incref( newvalue ) ); + + // Create a copy in which to store the validated values + Py_ssize_t size = PyTuple_GET_SIZE( newvalue ); + cppy::ptr tuplecopy = PyTuple_New( size ); + if( !tuplecopy ) + { + return 0; + } + + // Check the size match the expected size + Py_ssize_t expected_size = PyTuple_GET_SIZE( member->validate_context ); + if( size != expected_size ) + { + PyErr_Format( + PyExc_TypeError, + "The '%s' member on the '%s' object must be of a '%d-tuple'. " + "Got tuple of length %d instead", + PyUnicode_AsUTF8( member->name ), + Py_TYPE( pyobject_cast( atom ) )->tp_name, + expected_size, + size + ); + return 0; + } + + // Validate each single item + for( Py_ssize_t i = 0; i < size; ++i ) + { + Member* item_member = member_cast( PyTuple_GET_ITEM( member->validate_context, i ) ); + cppy::ptr item( cppy::incref( PyTuple_GET_ITEM( tupleptr.get(), i ) ) ); + cppy::ptr valid_item( item_member->full_validate( atom, Py_None, item.get() ) ); + if( !valid_item ) + { + return 0; + } + PyTuple_SET_ITEM( tuplecopy.get(), i, valid_item.release() ); + } + tupleptr = tuplecopy; + return tupleptr.release(); +} + + template PyObject* common_list_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { @@ -912,6 +981,7 @@ handlers[] = { str_handler, str_promote_handler, tuple_handler, + fixed_tuple_handler, list_handler, container_list_handler, set_handler, diff --git a/atom/tuple.py b/atom/tuple.py index 4fc92850..da89b702 100644 --- a/atom/tuple.py +++ b/atom/tuple.py @@ -1,10 +1,12 @@ # -------------------------------------------------------------------------------------- -# Copyright (c) 2013-2023, Nucleic Development Team. +# Copyright (c) 2013-2024, 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 Tuple as TTuple + from .catom import DefaultValue, Member, Validate from .instance import Instance from .typing_utils import extract_types, is_optional @@ -78,3 +80,79 @@ def clone(self): else: clone.item = None return clone + + +class FixedTuple(Member): + """A member which allows tuple values with a fixed number of items. + + Items are always validated and can be of different types. + Assignment will create a copy of the original tuple before validating the + items, since validation may change the item values. + + """ + + #: Members used to validate each element of the tuple. + items: TTuple[Member, ...] + + __slots__ = ("items",) + + def __init__(self, *items, default=None): + """Initialize a Tuple. + + Parameters + ---------- + items : Member, type, or tuple of types + A member to use for validating the types of items allowed in + the tuple. This can also be a type object or a tuple of types, + in which case it will be wrapped with an Instance member. + + default : tuple, optional + The default tuple of values. + + """ + mitems = [] + for i in items: + if not isinstance(i, Member): + opt, types = is_optional(extract_types(i)) + i = Instance(types, optional=opt) + mitems.append(i) + + self.items = mitems + + if default is None: + self.set_default_value_mode(DefaultValue.NonOptional, None) + else: + self.set_default_value_mode(DefaultValue.Static, default) + self.set_validate_mode(Validate.FixedTuple, tuple(mitems)) + + def set_name(self, name): + """Set the name of the member. + + This method ensures that the item member name is also updated. + + """ + super().set_name(name) + for i, item in enumerate(self.items): + item.set_name(name + f"|item_{i}") + + def set_index(self, index): + """Assign the index to this member. + + This method ensures that the item member index is also updated. + + """ + super().set_index(index) + for item in self.items: + item.set_index(index) + + def clone(self): + """Create a clone of the tuple. + + This will clone the internal tuple item if one is in use. + + """ + clone = super().clone() + clone.items = items_clone = tuple(i.clone() for i in self.items) + mode, _ = self.validate_mode + clone.set_validate_mode(mode, items_clone) + return clone diff --git a/atom/tuple.pyi b/atom/tuple.pyi index a7b6dcc5..e18326b9 100644 --- a/atom/tuple.pyi +++ b/atom/tuple.pyi @@ -1,11 +1,21 @@ # -------------------------------------------------------------------------------------- -# Copyright (c) 2021-2023, Nucleic Development Team. +# Copyright (c) 2021-2024, 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, Optional, Tuple as TTuple, Type, TypeVar, Union, overload +from typing import ( + Any, + Optional, + Tuple as TTuple, + Type, + TypeVar, + Union, + overload, +) + +from typing_extensions import Unpack from .catom import Member @@ -42,3 +52,42 @@ class Tuple(Member[TTuple[T, ...], TTuple[T, ...]]): def __new__( cls, kind: Member[T, Any], default: Optional[TTuple[T]] = None ) -> Tuple[T]: ... + +TT = TypeVar("TT", bound=tuple) + +# FIXME technically we can allow tuple of types in place of just types but that +# is not expected to serve often. + +class FixedTuple(Member[TT, TT]): + @overload + def __new__( + cls, *items: Unpack[TTuple[Member[T, Any]]], default: Optional[TTuple[T]] = None + ) -> FixedTuple[TTuple[T]]: ... + @overload + def __new__( + cls, + *items: Unpack[TTuple[Member[T, Any], Member[T1, Any]]], + default: Optional[TTuple[T, T1]] = None, + ) -> FixedTuple[TTuple[T, T1]]: ... + @overload + def __new__( + cls, + *items: Unpack[TTuple[Member[T, Any], Member[T1, Any], Member[T2, Any]]], + default: Optional[TTuple[T, T1, T2]] = None, + ) -> FixedTuple[TTuple[T, T1, T2]]: ... + @overload + def __new__( + cls, *items: Unpack[TTuple[Type[T]]], default: Optional[TTuple[T]] = None + ) -> FixedTuple[TTuple[T]]: ... + @overload + def __new__( + cls, + *items: Unpack[TTuple[Type[T], Type[T1]]], + default: Optional[TTuple[T, T1]] = None, + ) -> FixedTuple[TTuple[T, T1]]: ... + @overload + def __new__( + cls, + *items: Unpack[TTuple[Type[T], Type[T1], Type[T2]]], + default: Optional[TTuple[T, T1, T2]] = None, + ) -> FixedTuple[TTuple[T, T1, T2]]: ... diff --git a/docs/source/examples/example_doc_generator.py b/docs/source/examples/example_doc_generator.py index f3cbb2a4..578c99f6 100644 --- a/docs/source/examples/example_doc_generator.py +++ b/docs/source/examples/example_doc_generator.py @@ -5,11 +5,12 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -""" Generate the Example Documentation for the Atom Examples +"""Generate the Example Documentation for the Atom Examples Run as part of the documentation build script. """ + import os import re import sys diff --git a/examples/api/coersion.py b/examples/api/coersion.py index 3e7546ec..dcb81a14 100644 --- a/examples/api/coersion.py +++ b/examples/api/coersion.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Demonstration of the basic use of the Coerced member. +"""Demonstration of the basic use of the Coerced member.""" -""" from atom.api import Atom, Coerced diff --git a/examples/api/composition.py b/examples/api/composition.py index 8fe7c1dc..95deab59 100644 --- a/examples/api/composition.py +++ b/examples/api/composition.py @@ -5,7 +5,7 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -""" Demonstrate the use of Compostion of Atom objects. +"""Demonstrate the use of Compostion of Atom objects. 1. If the class has not been declared, use a ForwardTyped - Note the use of lambda, because "Person" is not yet defined @@ -16,6 +16,7 @@ - Provide a pre-created object in the constructor """ + from atom.api import Atom, ForwardTyped, Str, Typed diff --git a/examples/api/containers.py b/examples/api/containers.py index a79bd32a..664f2d35 100644 --- a/examples/api/containers.py +++ b/examples/api/containers.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Demonstration of the member handling containers. +"""Demonstration of the member handling containers.""" -""" from atom.api import Atom, ContainerList, Dict, List, Tuple diff --git a/examples/api/default_value.py b/examples/api/default_value.py index 57a6a174..04ee4309 100644 --- a/examples/api/default_value.py +++ b/examples/api/default_value.py @@ -5,7 +5,7 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -""" Demonstrate all the ways to initialize a value +"""Demonstrate all the ways to initialize a value 1. Pass the value directly 2. Assign the default value explicitly @@ -14,6 +14,7 @@ 5. Use a _default_* static method """ + import sys from atom.api import Atom, Int, Str diff --git a/examples/api/metadata.py b/examples/api/metadata.py index e20d98f9..9e48bd03 100644 --- a/examples/api/metadata.py +++ b/examples/api/metadata.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Example demonstrating the use of metadata to filter members. +"""Example demonstrating the use of metadata to filter members.""" -""" import sys from atom.api import Atom, Int, Str diff --git a/examples/api/numeric.py b/examples/api/numeric.py index df7feccb..b03538cf 100644 --- a/examples/api/numeric.py +++ b/examples/api/numeric.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Demonstration of the member handling numeric values. +"""Demonstration of the member handling numeric values.""" -""" import sys from atom.api import Atom, Bool, Float, Int diff --git a/examples/api/observe.py b/examples/api/observe.py index 647cb440..90268a8b 100644 --- a/examples/api/observe.py +++ b/examples/api/observe.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Demonstration of the use of static and dynamic observers. +"""Demonstration of the use of static and dynamic observers.""" -""" from atom.api import Atom, ChangeDict, Range, Str, Typed, observe diff --git a/examples/api/observe_hints.py b/examples/api/observe_hints.py index a7671891..15308d69 100644 --- a/examples/api/observe_hints.py +++ b/examples/api/observe_hints.py @@ -6,6 +6,7 @@ # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the use of static and dynamic observers.""" + from typing import Optional from atom.api import Atom, ChangeDict, observe diff --git a/examples/api/pickling.py b/examples/api/pickling.py index 54f2cfbc..9e4115f6 100644 --- a/examples/api/pickling.py +++ b/examples/api/pickling.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Demonstration of the basic use of the Coerced member. +"""Demonstration of the basic use of the Coerced member.""" -""" import pickle from atom.api import Atom, GetState, clone_if_needed diff --git a/examples/api/property.py b/examples/api/property.py index b4a0da8a..78412864 100644 --- a/examples/api/property.py +++ b/examples/api/property.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Demonstration of the basics of the Property member. +"""Demonstration of the basics of the Property member.""" -""" from atom.api import Atom, Int, Property, Str diff --git a/examples/api/range.py b/examples/api/range.py index 76764e64..22964b38 100644 --- a/examples/api/range.py +++ b/examples/api/range.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Demonstration of the ranges members. +"""Demonstration of the ranges members.""" -""" from atom.api import Atom, FloatRange, Range diff --git a/examples/tutorial/employee.py b/examples/tutorial/employee.py index 218fae36..18ad7563 100644 --- a/examples/tutorial/employee.py +++ b/examples/tutorial/employee.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Simple example of a class hierarchy built on atom. +"""Simple example of a class hierarchy built on atom.""" -""" import datetime from atom.api import ( diff --git a/examples/tutorial/hello_world.py b/examples/tutorial/hello_world.py index e29d29d2..c82be9ea 100644 --- a/examples/tutorial/hello_world.py +++ b/examples/tutorial/hello_world.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Hello world example: how to write an atom class. +"""Hello world example: how to write an atom class.""" -""" from __future__ import absolute_import, division, print_function, unicode_literals from atom.api import Atom, Str diff --git a/examples/tutorial/person.py b/examples/tutorial/person.py index 44126b4b..745e7cae 100644 --- a/examples/tutorial/person.py +++ b/examples/tutorial/person.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Simple class using atom and static observers. +"""Simple class using atom and static observers.""" -""" from atom.api import Atom, Bool, ChangeDict, Range, Str, observe diff --git a/examples/typehints/inventory.py b/examples/typehints/inventory.py index 40e33cfc..eb856296 100644 --- a/examples/typehints/inventory.py +++ b/examples/typehints/inventory.py @@ -6,6 +6,7 @@ # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Example on using type hints to create Atom subclasses.""" + from atom.api import Atom, ChangeDict, observe diff --git a/examples/typehints/static_type_checking.py b/examples/typehints/static_type_checking.py index 6a5eed17..24ca8623 100644 --- a/examples/typehints/static_type_checking.py +++ b/examples/typehints/static_type_checking.py @@ -6,6 +6,7 @@ # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the interaction between static and dynamic type validation.""" + from typing import List, Optional from atom.api import Atom, Int diff --git a/pyproject.toml b/pyproject.toml index eba071bd..34c3ea7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,3 +93,28 @@ __version__ = "{version}" [tool.pytest.ini_options] minversion = "6.0" + +[tool.coverage] + [tool.coverage.run] + branch = true + source = ["atom"] + + [tool.coverage.report] + # Regexes for lines to exclude from consideration + exclude_lines = [ + # Have to re-enable the standard pragma + "pragma: no cover", + + # Don't complain if tests don't hit defensive assertion code: + "raise NotImplementedError", + "pass", + + # Don't complain about abstract methods, they aren't run: + "@(abc\\.)?abstractmethod", + + # Don't complain about type checking + "if TYPE_CHECKING:", + + # Don't complain about ellipsis in overload + "\\.\\.\\.", + ] diff --git a/releasenotes.rst b/releasenotes.rst index 78aacfdb..b287665b 100644 --- a/releasenotes.rst +++ b/releasenotes.rst @@ -1,6 +1,12 @@ Atom Release Notes ================== +0.11.0 - unreleased +------------------- + +- add FixedTuple member to validate inhomogeneous tuple with fixed number of + elements PR #211 + 0.10.4 - 23/01/2024 ------------------- diff --git a/tests/datastructure/test_sortedmap.py b/tests/datastructure/test_sortedmap.py index ee3e230a..ed227274 100644 --- a/tests/datastructure/test_sortedmap.py +++ b/tests/datastructure/test_sortedmap.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the sortedmap that acts like an ordered dictionary. +"""Test the sortedmap that acts like an ordered dictionary.""" -""" import gc import pytest diff --git a/tests/test_atom.py b/tests/test_atom.py index a22377c6..64f78d97 100644 --- a/tests/test_atom.py +++ b/tests/test_atom.py @@ -13,6 +13,7 @@ The methods related to member observation are tested in test_observe.py """ + import gc import pickle from textwrap import dedent diff --git a/tests/test_atom_from_annotations.py b/tests/test_atom_from_annotations.py index 31f9773e..bc41a097 100644 --- a/tests/test_atom_from_annotations.py +++ b/tests/test_atom_from_annotations.py @@ -1,13 +1,12 @@ # ------------------------------------------------------------------------------ -# Copyright (c) 2021-2023, Nucleic Development Team. +# Copyright (c) 2021-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ------------------------------------------------------------------------------ -"""Test defining an atom class using typing annotations. +"""Test defining an atom class using typing annotations.""" -""" import logging import sys from collections import defaultdict @@ -36,6 +35,7 @@ Callable, DefaultDict, Dict, + FixedTuple, Float, Instance, Int, @@ -203,10 +203,15 @@ class A(Atom, use_annotations=True): (TDict[int, int], Dict(Int(), Int()), 1), (TDefaultDict[int, int], DefaultDict(Int(), Int()), 1), (TTuple[int], Tuple(), 0), - (TTuple[int], Tuple(Int()), 1), + (TTuple[int], FixedTuple(Int()), 1), (TTuple[int, ...], Tuple(Int()), 1), - (TTuple[int, float], Tuple(), 1), - (TTuple[tuple, int], Tuple(), 1), + (TTuple[int, float], FixedTuple(Int(), Float()), 1), + (TTuple[tuple, int], FixedTuple(Tuple(), Int()), 1), + ( + TTuple[TTuple[int, int], int], + FixedTuple(FixedTuple(Int(), Int()), Int()), + -1, + ), ] + ( [ @@ -220,10 +225,15 @@ class A(Atom, use_annotations=True): (dict[int, int], Dict(Int(), Int()), 1), (defaultdict[int, int], DefaultDict(Int(), Int()), 1), (tuple[int], Tuple(), 0), - (tuple[int], Tuple(Int()), 1), + (tuple[int], FixedTuple(Int()), 1), (tuple[int, ...], Tuple(Int()), 1), - (tuple[int, float], Tuple(), 1), - (tuple[tuple, int], Tuple(), 1), + (tuple[int, float], FixedTuple(Int(), Float()), 1), + (tuple[tuple, int], FixedTuple(Tuple(), Int()), 1), + ( + tuple[tuple[int, int], int], + FixedTuple(FixedTuple(Int(), Int()), Int()), + -1, + ), ] if sys.version_info >= (3, 9) else [] @@ -237,6 +247,8 @@ class A(Atom, use_annotations=True, type_containers=depth): if depth == 0: if isinstance(member, Dict): assert A.a.validate_mode[1] == (None, None) + elif isinstance(member, FixedTuple): + assert A.a.items == () else: assert A.a.item is None else: @@ -253,6 +265,9 @@ class A(Atom, use_annotations=True, type_containers=depth): assert type(k) is type(mk) assert type(v) is type(mv) assert f(A()) == mf(A()) + elif isinstance(member, FixedTuple): + for v, mv in zip(A.a.validate_mode[1], member.validate_mode[1]): + assert type(v) is type(mv) else: assert type(A.a.item) is type(member.item) # noqa: E721 if isinstance(member.item, List): @@ -271,6 +286,7 @@ class A(Atom, use_annotations=True, type_containers=depth): (Union[Any, None], Value, None), (object, Value, 2), (TCallable, Callable, lambda x: x), + (TTuple[int], FixedTuple, (1,)), (TList, List, [1]), (TSet, Set, {1}), (TDict, Dict, {1: 2}), @@ -280,6 +296,7 @@ class A(Atom, use_annotations=True, type_containers=depth): ] + ( [ + (tuple[int], FixedTuple, (1,)), (list, List, [1]), (set, Set, {1}), (dict, Dict, {1: 2}), diff --git a/tests/test_atomdefaultdict.py b/tests/test_atomdefaultdict.py index f0f3cdea..bf5d7d59 100644 --- a/tests/test_atomdefaultdict.py +++ b/tests/test_atomdefaultdict.py @@ -5,10 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the typed dictionary. +"""Test the typed dictionary.""" - -""" from collections import defaultdict import pytest diff --git a/tests/test_atomdict.py b/tests/test_atomdict.py index c049c761..ccb58867 100644 --- a/tests/test_atomdict.py +++ b/tests/test_atomdict.py @@ -5,10 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the typed dictionary. +"""Test the typed dictionary.""" - -""" import pytest from atom.api import Atom, Dict, Int, List, atomdict, atomlist diff --git a/tests/test_atomset.py b/tests/test_atomset.py index c08cb758..93de9f64 100644 --- a/tests/test_atomset.py +++ b/tests/test_atomset.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the typed dictionary. +"""Test the typed dictionary.""" -""" from operator import iand, ior, isub, ixor import pytest diff --git a/tests/test_default_values.py b/tests/test_default_values.py index 29fa0e34..832550dc 100644 --- a/tests/test_default_values.py +++ b/tests/test_default_values.py @@ -7,19 +7,20 @@ # -------------------------------------------------------------------------------------- """Tests for member validation handlers: - no_op_handler: unused as far as I can tell - static_handler - list_handler - dict_handler - delegate_handler: not tested here - call_object_handler: used for factory function or Typed/Instance with args - 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: advanced used case not used internally - member_method_object_handler +no_op_handler: unused as far as I can tell +static_handler +list_handler +dict_handler +delegate_handler: not tested here +call_object_handler: used for factory function or Typed/Instance with args +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: advanced used case not used internally +member_method_object_handler """ + import pytest from atom.api import ( diff --git a/tests/test_del_behaviors.py b/tests/test_del_behaviors.py index 7214e177..774affe0 100644 --- a/tests/test_del_behaviors.py +++ b/tests/test_del_behaviors.py @@ -7,16 +7,17 @@ # -------------------------------------------------------------------------------------- """Test the del behaviors - no_op_handler: not sure it is used - slot_handler: behavior leading to calling the default factory on next get - constant_handler - read_only_handler - event_handler - signal_handler - delegate_handler: not tested here (see test_delegate.py) - property_handler: not tested here (see test_property.py) +no_op_handler: not sure it is used +slot_handler: behavior leading to calling the default factory on next get +constant_handler +read_only_handler +event_handler +signal_handler +delegate_handler: not tested here (see test_delegate.py) +property_handler: not tested here (see test_property.py) """ + import pytest from atom.api import Atom, Constant, Event, Int, Member, ReadOnly, Signal diff --git a/tests/test_delegator.py b/tests/test_delegator.py index 3be09a22..01bf4e55 100644 --- a/tests/test_delegator.py +++ b/tests/test_delegator.py @@ -17,6 +17,7 @@ - del """ + import pytest from atom.api import ( diff --git a/tests/test_enum.py b/tests/test_enum.py index 5ec9b52f..10f86a80 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the enum member. +"""Test the enum member.""" -""" import pytest from atom.api import Enum diff --git a/tests/test_eventbinder.py b/tests/test_eventbinder.py index fcb28fa7..948f5454 100644 --- a/tests/test_eventbinder.py +++ b/tests/test_eventbinder.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the notification mechanisms. +"""Test the notification mechanisms.""" -""" import gc import operator import sys diff --git a/tests/test_examples.py b/tests/test_examples.py index cd14bffe..908a37fb 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the examples. +"""Test the examples.""" -""" import os import pytest diff --git a/tests/test_get_behaviors.py b/tests/test_get_behaviors.py index b0292867..72e5264c 100644 --- a/tests/test_get_behaviors.py +++ b/tests/test_get_behaviors.py @@ -7,21 +7,22 @@ # -------------------------------------------------------------------------------------- """Test the get behaviors - no_op_handler: : tested here - slot_handler: standard one tested through other tests (post_get, ...) - event_handler: not tested here (see test_observe.py and - test_event_binder.py) - signal_handler: not tested here (see test_observe.py) - delegate_handler: not tested here (see test_delegate.py) - property_handler: not tested here (see test_property.py) - cached_property_handler: not tested here (see test_property.py) - call_object_object_handler: tested here - call_object_object_name_handler: tested here - object_method_handler: tested here - object_method_name_handler: tested here - member_method_object_handler: method defined on a Member subclass +no_op_handler: : tested here +slot_handler: standard one tested through other tests (post_get, ...) +event_handler: not tested here (see test_observe.py and + test_event_binder.py) +signal_handler: not tested here (see test_observe.py) +delegate_handler: not tested here (see test_delegate.py) +property_handler: not tested here (see test_property.py) +cached_property_handler: not tested here (see test_property.py) +call_object_object_handler: tested here +call_object_object_name_handler: tested here +object_method_handler: tested here +object_method_name_handler: tested here +member_method_object_handler: method defined on a Member subclass """ + import pytest from atom.api import Atom, GetAttr, Int, Value diff --git a/tests/test_getstate_behaviors.py b/tests/test_getstate_behaviors.py index d5c3e9a4..949819e3 100644 --- a/tests/test_getstate_behaviors.py +++ b/tests/test_getstate_behaviors.py @@ -7,14 +7,15 @@ # -------------------------------------------------------------------------------------- """Test the getstate behaviors - include_handler: tested here - exclude_handler: tested here - include_non_default_handler: tested here - property_handler: tested in test_property - member_method_object_handler: tested here - object_method_name_handler: tested here +include_handler: tested here +exclude_handler: tested here +include_non_default_handler: tested here +property_handler: tested in test_property +member_method_object_handler: tested here +object_method_name_handler: tested here """ + import pickle import pytest diff --git a/tests/test_member.py b/tests/test_member.py index 6944b7b1..ce79bf75 100644 --- a/tests/test_member.py +++ b/tests/test_member.py @@ -38,6 +38,7 @@ """ + import pytest from atom.api import ( @@ -45,6 +46,7 @@ DefaultValue, Dict, Event, + FixedTuple, ForwardInstance, ForwardSubclass, ForwardTyped, @@ -332,6 +334,16 @@ def test_cloning_containers_member(untyped, typed): assert isinstance(cv, type(v)) +def test_cloning_fixed_tuple(): + typed = FixedTuple(int) + typed.set_index(5) + cl2 = typed.clone() + assert cl2.index == typed.index + for v, cv in zip(typed.items, cl2.items): + assert cv is not v + assert isinstance(cv, type(v)) + + # XXX should the kwargs be copied rather than simply re-assigned @pytest.mark.parametrize( "member, cloned_attributes", diff --git a/tests/test_observe.py b/tests/test_observe.py index ad4881b4..243828f7 100644 --- a/tests/test_observe.py +++ b/tests/test_observe.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the notification mechanisms. +"""Test the notification mechanisms.""" -""" import pytest from atom.api import ( diff --git a/tests/test_post_behaviors.py b/tests/test_post_behaviors.py index 8c323eb0..b1c97506 100644 --- a/tests/test_post_behaviors.py +++ b/tests/test_post_behaviors.py @@ -16,6 +16,7 @@ member_method_object_old_new_handler: Method defined on a Member subclass """ + import pytest from atom.api import Int, PostGetAttr, PostSetAttr, PostValidate diff --git a/tests/test_property.py b/tests/test_property.py index 35efec18..1daafea9 100644 --- a/tests/test_property.py +++ b/tests/test_property.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the property and cached property member +"""Test the property and cached property member""" -""" import pytest from atom.api import ( diff --git a/tests/test_set_behaviors.py b/tests/test_set_behaviors.py index 3d0ba307..9c53e493 100644 --- a/tests/test_set_behaviors.py +++ b/tests/test_set_behaviors.py @@ -7,21 +7,22 @@ # -------------------------------------------------------------------------------------- """Test the set behaviors - no_op_handler: do nothing on setting - slot_handler: standard one tested through other tests (post_set, ...) - constant_handler: prevent to set a value - read_only_handler: allow a single set - event_handler: not tested here (see test_observe.py) - signal_handler: siganls are not settable - delegate_handler: not tested here (see test_delegate.py) - property_handler: not tested here (see test_property.py) - call_object_object_value_handler: use a custom function - call_object_object_name_value_handler: use a custom function - object_method_value_handler: use an object method - object_method_name_value_handler: use an object method - member_method_object_value_handler: method defined on a Member subclass +no_op_handler: do nothing on setting +slot_handler: standard one tested through other tests (post_set, ...) +constant_handler: prevent to set a value +read_only_handler: allow a single set +event_handler: not tested here (see test_observe.py) +signal_handler: siganls are not settable +delegate_handler: not tested here (see test_delegate.py) +property_handler: not tested here (see test_property.py) +call_object_object_value_handler: use a custom function +call_object_object_name_value_handler: use a custom function +object_method_value_handler: use an object method +object_method_name_value_handler: use an object method +member_method_object_value_handler: method defined on a Member subclass """ + import pytest from atom.api import Atom, Constant, Int, ReadOnly, SetAttr, Signal diff --git a/tests/test_signalconnector.py b/tests/test_signalconnector.py index 6691bc5a..1025f6f0 100644 --- a/tests/test_signalconnector.py +++ b/tests/test_signalconnector.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Test the signal connectors. +"""Test the signal connectors.""" -""" import gc import operator import sys diff --git a/tests/test_typing_utils.py b/tests/test_typing_utils.py index 93b0a601..3456f55b 100644 --- a/tests/test_typing_utils.py +++ b/tests/test_typing_utils.py @@ -6,9 +6,10 @@ # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test typing utilities.""" + import sys from collections.abc import Iterable -from typing import Dict, List, Optional, Set, TypeVar, Union +from typing import Dict, List, Optional, Set, Tuple, TypeVar, Union import pytest @@ -24,6 +25,7 @@ @pytest.mark.parametrize( "ty, outputs", [ + (Tuple[int], (tuple,)), (List[int], (list,)), (Dict[str, int], (dict,)), (Set[int], (set,)), @@ -34,6 +36,7 @@ ] + ( [ + (tuple[int], (tuple,)), (list[int], (list,)), (dict[str, int], (dict,)), (set[int], (set,)), diff --git a/tests/test_validators.py b/tests/test_validators.py index 0f4c61cd..87261f06 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -7,37 +7,39 @@ # ------------------------------------------------------------------------------------------------------ """Tests for member validation handlers: - no_op_handler - bool_handler - int_handler - int_promote_handler - long_handler - long_promote_handler - float_handler - float_promote_handler - str_handler - str_promote_handler - unicode_handler - unicode_promote_handler - tuple_handler - list_handler - container_list_handler - set_handler - dict_handler - instance_handler - typed_handler - subclass_handler - enum_handler - callable_handler - float_range_handler - range_handler - coerced_handler - delegate_handler: not tested here - object_method_old_new_handler: used when defining validate on Atom subclass - object_method_name_old_new_handler: unused as far as I can tell - member_method_object_old_new_handler: used in ForwardType/Instance/Subclass +no_op_handler +bool_handler +int_handler +int_promote_handler +long_handler +long_promote_handler +float_handler +float_promote_handler +str_handler +str_promote_handler +unicode_handler +unicode_promote_handler +tuple_handler +fixed_tuple_handler +list_handler +container_list_handler +set_handler +dict_handler +instance_handler +typed_handler +subclass_handler +enum_handler +callable_handler +float_range_handler +range_handler +coerced_handler +delegate_handler: not tested here +object_method_old_new_handler: used when defining validate on Atom subclass +object_method_name_old_new_handler: unused as far as I can tell +member_method_object_old_new_handler: used in ForwardType/Instance/Subclass """ + import sys from typing import List as TList, Optional, Sequence, Set as TSet, Union @@ -56,6 +58,7 @@ Dict, Enum, Event, + FixedTuple, Float, FloatRange, ForwardInstance, @@ -132,6 +135,10 @@ def c(x: object) -> int: (Tuple(int), [(1,)], [(1,)], [(1.0,), (None,)]), (Tuple(TSet[int]), [({1},)], [({1},)], [(1.0,), (None,)]), (Tuple(Optional[int]), [(1, None)], [(1, None)], [("",)]), + (FixedTuple(Int()), [(1,)], [(1,)], [(None,), (1, 2)]), + (FixedTuple(Int(), Str()), [(1, "")], [(1, "")], [(None,), (1, 2)]), + (FixedTuple(int), [(1,)], [(1,)], [(None,), (1, 2)]), + (FixedTuple(TSet[int]), [({1},)], [({1},)], [(None,), (1, 2)]), (List(), [[1]], [[1]], [(1,)]), (List(Int()), [[1]], [[1]], [[1.0]]), (List(float), [[1.0]], [[1.0]], [[1], [None]]), @@ -252,6 +259,7 @@ class MemberTest(Atom): [ (List(), "List", 1, "Member or None"), (Tuple(), "Tuple", 1, "Member or None"), + (FixedTuple(int), "FixedTuple", 1, "tuple of types or Members"), (ContainerList(), "ContainerList", 1, "Member or None"), (Set(), "Set", 1, "Member or None"), (Dict(), "Dict", 1, "2-tuple of Member or None"), diff --git a/tests/test_version.py b/tests/test_version.py index 7a913ce6..de58668e 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -5,9 +5,8 @@ # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- -"""Tests getting the version. +"""Tests getting the version.""" -""" from atom import version diff --git a/tests/type_checking/test_annotations.yml b/tests/type_checking/test_annotations.yml index 00aaa5ef..12aba27f 100644 --- a/tests/type_checking/test_annotations.yml +++ b/tests/type_checking/test_annotations.yml @@ -5,7 +5,7 @@ # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ -- case: annotated-member +- case: annotated_member parametrized: - member: List annotation: List[int] diff --git a/tests/type_checking/test_coerced.yml b/tests/type_checking/test_coerced.yml index ba37aeef..097dfe01 100644 --- a/tests/type_checking/test_coerced.yml +++ b/tests/type_checking/test_coerced.yml @@ -5,7 +5,7 @@ # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ -- case: coerced-no-coercer +- case: coerced_no_coercer parametrized: - member: Coerced member_instance: Coerced(int) @@ -42,7 +42,7 @@ reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" -- case: coerced-coercer +- case: coerced_coercer parametrized: - member: Coerced member_instance: Coerced(io.StringIO, coercer=coercer) diff --git a/tests/type_checking/test_dict.yml b/tests/type_checking/test_dict.yml index 7d1337e7..d657bc27 100644 --- a/tests/type_checking/test_dict.yml +++ b/tests/type_checking/test_dict.yml @@ -197,7 +197,7 @@ reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" -- case: dict-with-default +- case: dict_with_default parametrized: # Dict with typed key - member: Dict diff --git a/tests/type_checking/test_enum.yml b/tests/type_checking/test_enum.yml index cf592119..e43d2547 100644 --- a/tests/type_checking/test_enum.yml +++ b/tests/type_checking/test_enum.yml @@ -25,7 +25,7 @@ reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" A().m = 1 -- case: enum-methods +- case: enum_methods main: | from atom.api import Atom, Enum diff --git a/tests/type_checking/test_generic_aliases.yml b/tests/type_checking/test_generic_aliases.yml index 93aff172..16cb186e 100644 --- a/tests/type_checking/test_generic_aliases.yml +++ b/tests/type_checking/test_generic_aliases.yml @@ -14,7 +14,7 @@ reveal_type(m) # N: Revealed type is "atom.catom.Member[builtins.int, builtins.str]" -- case: generic-alias-as-type +- case: generic_alias_as_type skip: sys.version_info < (3, 9) parametrized: - member: Typed diff --git a/tests/type_checking/test_list.yml b/tests/type_checking/test_list.yml index be96b923..8bf527a5 100644 --- a/tests/type_checking/test_list.yml +++ b/tests/type_checking/test_list.yml @@ -103,7 +103,7 @@ reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" -- case: container-list +- case: container_list parametrized: # List with no defaults - member: ContainerList diff --git a/tests/type_checking/test_property.yml b/tests/type_checking/test_property.yml index d25771f9..cc5cd28e 100644 --- a/tests/type_checking/test_property.yml +++ b/tests/type_checking/test_property.yml @@ -5,7 +5,7 @@ # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ -- case: property-empty +- case: property_empty main: | from atom.api import Atom, Property @@ -16,7 +16,7 @@ reveal_type(A().m) # N: Revealed type is "Never" -- case: property-no-setter +- case: property_no_setter main: | from atom.api import Atom, Property @@ -60,7 +60,7 @@ reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" -- case: cached-property +- case: cached_property main: | from atom.api import Atom, cached_property diff --git a/tests/type_checking/test_scalars.yml b/tests/type_checking/test_scalars.yml index 71d8c9ae..2f6e2352 100644 --- a/tests/type_checking/test_scalars.yml +++ b/tests/type_checking/test_scalars.yml @@ -5,7 +5,7 @@ # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ -- case: scalars-no-annotations +- case: scalars_no_annotations parametrized: - member: Value member_instance: Value() @@ -205,7 +205,7 @@ reveal_type(A.m) # N: Revealed type is "atom.scalars.Callable[def (*Any, **Any) -> Any]" reveal_type(A().m) # N: Revealed type is "def (*Any, **Any) -> Any" -- case: str-with-default +- case: str_with_default main: | from atom.api import Atom, Str @@ -215,7 +215,7 @@ reveal_type(A.m) # N: Revealed type is "atom.scalars.Str[builtins.str]" reveal_type(A().m) # N: Revealed type is "builtins.str" -- case: bytes-with-default +- case: bytes_with_default main: | from atom.api import Atom, Bytes diff --git a/tests/type_checking/test_tuple.yml b/tests/type_checking/test_tuple.yml index 8f854ab8..99d9118f 100644 --- a/tests/type_checking/test_tuple.yml +++ b/tests/type_checking/test_tuple.yml @@ -98,3 +98,39 @@ reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" + +- case: fixed_tuple + # 3.8 uses Tuple in response 3.9+ tuple + skip: sys.version_info < (3, 9) + parametrized: + # Tuple with no defaults + - member: FixedTuple + member_instance: FixedTuple(int) + member_type: atom.tuple.FixedTuple[tuple[builtins.int]] + member_value_type: tuple[builtins.int] + - member: FixedTuple + member_instance: FixedTuple(int, float) + member_type: atom.tuple.FixedTuple[tuple[builtins.int, builtins.float]] + member_value_type: tuple[builtins.int, builtins.float] + - member: FixedTuple + member_instance: FixedTuple(int, float, str) + member_type: atom.tuple.FixedTuple[tuple[builtins.int, builtins.float, builtins.str]] + member_value_type: tuple[builtins.int, builtins.float, builtins.str] + - member: FixedTuple, Int + member_instance: FixedTuple(Int()) + member_type: atom.tuple.FixedTuple[tuple[builtins.int]] + member_value_type: tuple[builtins.int] + # Tuple with defaults + - member: FixedTuple + member_instance: FixedTuple(int, default=(3,)) + member_type: atom.tuple.FixedTuple[tuple[builtins.int]] + member_value_type: tuple[builtins.int] + main: | + from atom.api import Atom, {{ member }} + + class A(Atom): + m = {{ member_instance }} + + reveal_type(A.m) # N: Revealed type is "{{ member_type }}" + reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" + diff --git a/tests/type_checking/test_typed_instance.yml b/tests/type_checking/test_typed_instance.yml index 855a4184..8db10fe2 100644 --- a/tests/type_checking/test_typed_instance.yml +++ b/tests/type_checking/test_typed_instance.yml @@ -5,7 +5,7 @@ # # The full license is in the file LICENSE, distributed with this software. # ------------------------------------------------------------------------------------------------------ -- case: typed-no-annotations +- case: typed_no_annotations parametrized: # Typed inferred optional - member: Typed @@ -131,7 +131,7 @@ reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" -- case: instance-no-annotations +- case: instance_no_annotations parametrized: # Instance inferred optional - member: Instance