Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions .coveragerc

This file was deleted.

13 changes: 0 additions & 13 deletions .flake8

This file was deleted.

6 changes: 1 addition & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: |
Expand Down
6 changes: 3 additions & 3 deletions atom/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -120,5 +119,6 @@
"Tuple",
"ForwardTyped",
"Typed",
"FixedTuple",
"ChangeDict",
]
1 change: 1 addition & 0 deletions atom/catom.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ class Validate(IntEnum):
Dict = ...
DefaultDict = ...
Enum = ...
FixedTuple = ...
Float = ...
FloatPromote = ...
FloatRange = ...
Expand Down
1 change: 1 addition & 0 deletions atom/meta/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions atom/meta/annotation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions atom/meta/atom_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions atom/meta/member_modifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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
#
1 change: 1 addition & 0 deletions atom/meta/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion atom/src/behaviors.h
Original file line number Diff line number Diff line change
@@ -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.
|
Expand Down Expand Up @@ -132,6 +132,7 @@ enum Mode
Str,
StrPromote,
Tuple,
FixedTuple,
List,
ContainerList,
Set,
Expand Down
1 change: 1 addition & 0 deletions atom/src/enumtypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) );
Expand Down
70 changes: 70 additions & 0 deletions atom/src/validatebehavior.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) )
Expand Down Expand Up @@ -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 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be moved to after the size check

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<typename ListFactory> PyObject*
common_list_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue )
{
Expand Down Expand Up @@ -912,6 +981,7 @@ handlers[] = {
str_handler,
str_promote_handler,
tuple_handler,
fixed_tuple_handler,
list_handler,
container_list_handler,
set_handler,
Expand Down
80 changes: 79 additions & 1 deletion atom/tuple.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Loading