easyscience 1.3.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {easyscience-1.3.0 → easyscience-2.0.0}/LICENSE +1 -1
  2. {easyscience-1.3.0 → easyscience-2.0.0}/PKG-INFO +3 -6
  3. {easyscience-1.3.0 → easyscience-2.0.0}/pyproject.toml +3 -6
  4. easyscience-2.0.0/src/easyscience/__init__.py +24 -0
  5. easyscience-2.0.0/src/easyscience/__version__.py +1 -0
  6. easyscience-2.0.0/src/easyscience/base_classes/__init__.py +9 -0
  7. easyscience-2.0.0/src/easyscience/base_classes/based_base.py +198 -0
  8. easyscience-1.3.0/src/easyscience/Objects/Groups.py → easyscience-2.0.0/src/easyscience/base_classes/collection_base.py +17 -21
  9. easyscience-2.0.0/src/easyscience/base_classes/obj_base.py +162 -0
  10. easyscience-2.0.0/src/easyscience/fitting/calculators/__init__.py +7 -0
  11. easyscience-1.3.0/src/easyscience/Objects/Inferface.py → easyscience-2.0.0/src/easyscience/fitting/calculators/interface_factory.py +12 -21
  12. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/fitter.py +3 -19
  13. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/minimizers/__init__.py +2 -2
  14. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/minimizers/minimizer_base.py +9 -30
  15. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/minimizers/minimizer_bumps.py +31 -20
  16. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/minimizers/minimizer_dfo.py +23 -12
  17. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/minimizers/minimizer_lmfit.py +25 -14
  18. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/multi_fitter.py +4 -5
  19. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/global_object/global_object.py +3 -7
  20. easyscience-2.0.0/src/easyscience/global_object/hugger/__init__.py +4 -0
  21. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/global_object/hugger/hugger.py +3 -6
  22. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/global_object/hugger/property.py +4 -7
  23. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/global_object/logger.py +2 -5
  24. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/global_object/map.py +2 -5
  25. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/global_object/undo_redo.py +5 -8
  26. easyscience-2.0.0/src/easyscience/io/__init__.py +12 -0
  27. easyscience-1.3.0/src/easyscience/Utils/io/template.py → easyscience-2.0.0/src/easyscience/io/serializer_base.py +34 -55
  28. easyscience-2.0.0/src/easyscience/io/serializer_component.py +80 -0
  29. easyscience-2.0.0/src/easyscience/io/serializer_dict.py +54 -0
  30. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/job/analysis.py +9 -11
  31. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/job/experiment.py +4 -4
  32. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/job/job.py +7 -7
  33. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/job/theoreticalmodel.py +4 -4
  34. {easyscience-1.3.0/src/easyscience/Utils/io → easyscience-2.0.0/src/easyscience/legacy}/dict.py +9 -9
  35. {easyscience-1.3.0/src/easyscience/Utils/io → easyscience-2.0.0/src/easyscience/legacy}/json.py +8 -60
  36. easyscience-1.3.0/src/easyscience/Objects/core.py → easyscience-2.0.0/src/easyscience/legacy/legacy_core.py +9 -12
  37. {easyscience-1.3.0/src/easyscience/Utils/io → easyscience-2.0.0/src/easyscience/legacy}/xml.py +8 -11
  38. easyscience-2.0.0/src/easyscience/models/__init__.py +3 -0
  39. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/models/polynomial.py +11 -47
  40. easyscience-2.0.0/src/easyscience/utils/__init__.py +3 -0
  41. {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.0.0/src/easyscience/utils}/classTools.py +8 -11
  42. {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.0.0/src/easyscience/utils}/classUtils.py +2 -5
  43. {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.0.0/src/easyscience/utils}/decorators.py +2 -5
  44. {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.0.0/src/easyscience/utils}/string.py +2 -6
  45. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/variable/descriptor_any_type.py +2 -2
  46. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/variable/descriptor_array.py +4 -4
  47. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/variable/descriptor_base.py +7 -7
  48. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/variable/descriptor_bool.py +2 -2
  49. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/variable/descriptor_number.py +84 -15
  50. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/variable/descriptor_str.py +2 -2
  51. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/variable/parameter.py +329 -193
  52. easyscience-1.3.0/src/easyscience/Constraints.py +0 -498
  53. easyscience-1.3.0/src/easyscience/Datasets/__init__.py +0 -6
  54. easyscience-1.3.0/src/easyscience/Datasets/xarray.py +0 -823
  55. easyscience-1.3.0/src/easyscience/Objects/ObjectClasses.py +0 -367
  56. easyscience-1.3.0/src/easyscience/Objects/__init__.py +0 -6
  57. easyscience-1.3.0/src/easyscience/REDIRECT.py +0 -9
  58. easyscience-1.3.0/src/easyscience/Utils/Exceptions.py +0 -15
  59. easyscience-1.3.0/src/easyscience/Utils/__init__.py +0 -6
  60. easyscience-1.3.0/src/easyscience/Utils/io/__init__.py +0 -6
  61. easyscience-1.3.0/src/easyscience/__init__.py +0 -31
  62. easyscience-1.3.0/src/easyscience/__version__.py +0 -1
  63. easyscience-1.3.0/src/easyscience/global_object/hugger/__init__.py +0 -6
  64. easyscience-1.3.0/src/easyscience/models/__init__.py +0 -6
  65. {easyscience-1.3.0 → easyscience-2.0.0}/.gitignore +0 -0
  66. {easyscience-1.3.0 → easyscience-2.0.0}/README.md +0 -0
  67. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/__init__.py +0 -0
  68. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/available_minimizers.py +0 -0
  69. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/minimizers/factory.py +0 -0
  70. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/fitting/minimizers/utils.py +0 -0
  71. {easyscience-1.3.0 → easyscience-2.0.0}/src/easyscience/global_object/__init__.py +0 -0
  72. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/job/__init__.py +0 -0
  73. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.0.0/src/easyscience}/variable/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  BSD 3-Clause License
2
2
 
3
- Copyright (c) 2024, Easyscience contributors (https://github.com/EasyScience)
3
+ Copyright (c) 2025, Easyscience contributors (https://github.com/EasyScience)
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: easyscience
3
- Version: 1.3.0
3
+ Version: 2.0.0
4
4
  Summary: Generic logic for easyScience libraries
5
5
  Project-URL: homepage, https://github.com/EasyScience/EasyScience
6
6
  Project-URL: documentation, https://easyscience.github.io/EasyScience/
7
7
  Author: EasyScience contributors
8
8
  License: BSD 3-Clause License
9
9
 
10
- Copyright (c) 2024, Easyscience contributors (https://github.com/EasyScience)
10
+ Copyright (c) 2025, Easyscience contributors (https://github.com/EasyScience)
11
11
  All rights reserved.
12
12
 
13
13
  Redistribution and use in source and binary forms, with or without
@@ -40,21 +40,18 @@ Classifier: License :: OSI Approved :: BSD License
40
40
  Classifier: Operating System :: OS Independent
41
41
  Classifier: Programming Language :: Python :: 3
42
42
  Classifier: Programming Language :: Python :: 3 :: Only
43
- Classifier: Programming Language :: Python :: 3.10
44
43
  Classifier: Programming Language :: Python :: 3.11
45
44
  Classifier: Programming Language :: Python :: 3.12
46
45
  Classifier: Programming Language :: Python :: 3.13
47
46
  Classifier: Topic :: Scientific/Engineering
48
- Requires-Python: >=3.10
47
+ Requires-Python: >=3.11
49
48
  Requires-Dist: asteval
50
49
  Requires-Dist: bumps
51
50
  Requires-Dist: dfo-ls
52
51
  Requires-Dist: lmfit
53
52
  Requires-Dist: numpy
54
- Requires-Dist: pint
55
53
  Requires-Dist: scipp
56
54
  Requires-Dist: uncertainties
57
- Requires-Dist: xarray
58
55
  Provides-Extra: dev
59
56
  Requires-Dist: build; extra == 'dev'
60
57
  Requires-Dist: codecov; extra == 'dev'
@@ -21,13 +21,12 @@ classifiers = [
21
21
  "Topic :: Scientific/Engineering",
22
22
  "Programming Language :: Python :: 3",
23
23
  "Programming Language :: Python :: 3 :: Only",
24
- "Programming Language :: Python :: 3.10",
25
24
  "Programming Language :: Python :: 3.11",
26
25
  "Programming Language :: Python :: 3.12",
27
26
  "Programming Language :: Python :: 3.13",
28
27
  "Development Status :: 3 - Alpha"
29
28
  ]
30
- requires-python = ">=3.10"
29
+ requires-python = ">=3.11"
31
30
  dependencies = [
32
31
  "asteval",
33
32
  "bumps",
@@ -35,8 +34,6 @@ dependencies = [
35
34
  "lmfit",
36
35
  "numpy",
37
36
  "uncertainties",
38
- "xarray",
39
- "pint", # Only to ensure that unit is reported as dimensionless rather than empty string
40
37
  "scipp"
41
38
  ]
42
39
 
@@ -75,6 +72,7 @@ packages = ["src"]
75
72
 
76
73
  [tool.hatch.build.targets.wheel]
77
74
  packages = ["src/easyscience"]
75
+ exclude = ["src/easyscience/legacy"]
78
76
 
79
77
  [tool.coverage.run]
80
78
  source = ["src/easyscience"]
@@ -128,10 +126,9 @@ force-single-line = true
128
126
  legacy_tox_ini = """
129
127
  [tox]
130
128
  isolated_build = True
131
- envlist = py{3.10,3.11,3.12,3.13}
129
+ envlist = py{3.11,3.12,3.13}
132
130
  [gh-actions]
133
131
  python =
134
- 3.10: py310
135
132
  3.11: py311
136
133
  3.12: py312
137
134
  3.13: py313
@@ -0,0 +1,24 @@
1
+ from .global_object import GlobalObject
2
+
3
+ # Must be executed before any other imports
4
+ global_object = GlobalObject()
5
+ global_object.instantiate_stack()
6
+ global_object.stack.enabled = False
7
+
8
+
9
+ from .__version__ import __version__ as __version__ # noqa: E402
10
+ from .base_classes import ObjBase # noqa: E402
11
+ from .fitting import AvailableMinimizers # noqa: E402
12
+ from .fitting import Fitter # noqa: E402
13
+ from .variable import DescriptorNumber # noqa: E402
14
+ from .variable import Parameter # noqa: E402
15
+
16
+ __all__ = [
17
+ __version__,
18
+ global_object,
19
+ ObjBase,
20
+ AvailableMinimizers,
21
+ Fitter,
22
+ DescriptorNumber,
23
+ Parameter,
24
+ ]
@@ -0,0 +1 @@
1
+ __version__ = '2.0.0'
@@ -0,0 +1,9 @@
1
+ from .based_base import BasedBase
2
+ from .collection_base import CollectionBase
3
+ from .obj_base import ObjBase
4
+
5
+ __all__ = [
6
+ BasedBase,
7
+ CollectionBase,
8
+ ObjBase,
9
+ ]
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ # SPDX-FileCopyrightText: 2025 EasyScience contributors <core@easyscience.software>
4
+ # SPDX-License-Identifier: BSD-3-Clause
5
+ # © 2021-2025 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
6
+ from inspect import getfullargspec
7
+ from typing import TYPE_CHECKING
8
+ from typing import Iterable
9
+ from typing import List
10
+ from typing import Optional
11
+ from typing import Set
12
+
13
+ from easyscience import global_object
14
+
15
+ from ..io import SerializerComponent
16
+ from ..variable import Parameter
17
+ from ..variable.descriptor_base import DescriptorBase
18
+
19
+ if TYPE_CHECKING:
20
+ from ..fitting.calculators import InterfaceFactoryTemplate
21
+
22
+
23
+ class BasedBase(SerializerComponent):
24
+ __slots__ = ['_name', '_global_object', 'user_data', '_kwargs']
25
+
26
+ _REDIRECT = {}
27
+
28
+ def __init__(self, name: str, interface: Optional[InterfaceFactoryTemplate] = None, unique_name: Optional[str] = None):
29
+ self._global_object = global_object
30
+ if unique_name is None:
31
+ unique_name = self._global_object.generate_unique_name(self.__class__.__name__)
32
+ self._unique_name = unique_name
33
+ self._name = name
34
+ self._global_object.map.add_vertex(self, obj_type='created')
35
+ self.interface = interface
36
+ self.user_data: dict = {}
37
+
38
+ @property
39
+ def _arg_spec(self) -> Set[str]:
40
+ base_cls = getattr(self, '__old_class__', self.__class__)
41
+ spec = getfullargspec(base_cls.__init__)
42
+ names = set(spec.args[1:])
43
+ return names
44
+
45
+ def __reduce__(self):
46
+ """
47
+ Make the class picklable.
48
+ Due to the nature of the dynamic class definitions special measures need to be taken.
49
+
50
+ :return: Tuple consisting of how to make the object
51
+ :rtype: tuple
52
+ """
53
+ state = self.encode()
54
+ cls = getattr(self, '__old_class__', self.__class__)
55
+ return cls.from_dict, (state,)
56
+
57
+ @property
58
+ def unique_name(self) -> str:
59
+ """Get the unique name of the object."""
60
+ return self._unique_name
61
+
62
+ @unique_name.setter
63
+ def unique_name(self, new_unique_name: str):
64
+ """Set a new unique name for the object. The old name is still kept in the map.
65
+
66
+ :param new_unique_name: New unique name for the object"""
67
+ if not isinstance(new_unique_name, str):
68
+ raise TypeError('Unique name has to be a string.')
69
+ self._unique_name = new_unique_name
70
+ self._global_object.map.add_vertex(self)
71
+
72
+ @property
73
+ def name(self) -> str:
74
+ """
75
+ Get the common name of the object.
76
+
77
+ :return: Common name of the object
78
+ """
79
+ return self._name
80
+
81
+ @name.setter
82
+ def name(self, new_name: str):
83
+ """
84
+ Set a new common name for the object.
85
+
86
+ :param new_name: New name for the object
87
+ :return: None
88
+ """
89
+ self._name = new_name
90
+
91
+ @property
92
+ def interface(self) -> InterfaceFactoryTemplate:
93
+ """
94
+ Get the current interface of the object
95
+ """
96
+ return self._interface
97
+
98
+ @interface.setter
99
+ def interface(self, new_interface: InterfaceFactoryTemplate):
100
+ """
101
+ Set the current interface to the object and generate bindings if possible. iF.e.
102
+ ```
103
+ def __init__(self, bar, interface=None, **kwargs):
104
+ super().__init__(self, **kwargs)
105
+ self.foo = bar
106
+ self.interface = interface # As final step after initialization to set correct bindings.
107
+ ```
108
+ """
109
+ self._interface = new_interface
110
+ if new_interface is not None:
111
+ self.generate_bindings()
112
+
113
+ def generate_bindings(self):
114
+ """
115
+ Generate or re-generate bindings to an interface (if exists)
116
+
117
+ :raises: AttributeError
118
+ """
119
+ if self.interface is None:
120
+ raise AttributeError('Interface error for generating bindings. `interface` has to be set.')
121
+ interfaceable_children = [
122
+ key
123
+ for key in self._global_object.map.get_edges(self)
124
+ if issubclass(type(self._global_object.map.get_item_by_key(key)), BasedBase)
125
+ ]
126
+ for child_key in interfaceable_children:
127
+ child = self._global_object.map.get_item_by_key(child_key)
128
+ child.interface = self.interface
129
+ self.interface.generate_bindings(self)
130
+
131
+ def switch_interface(self, new_interface_name: str):
132
+ """
133
+ Switch or create a new interface.
134
+ """
135
+ if self.interface is None:
136
+ raise AttributeError('Interface error for generating bindings. `interface` has to be set.')
137
+ self.interface.switch(new_interface_name)
138
+ self.generate_bindings()
139
+
140
+ def get_parameters(self) -> List[Parameter]:
141
+ """
142
+ Get all parameter objects as a list.
143
+
144
+ :return: List of `Parameter` objects.
145
+ """
146
+ par_list = []
147
+ for key, item in self._kwargs.items():
148
+ if hasattr(item, 'get_parameters'):
149
+ par_list = [*par_list, *item.get_parameters()]
150
+ elif isinstance(item, Parameter):
151
+ par_list.append(item)
152
+ return par_list
153
+
154
+ def _get_linkable_attributes(self) -> List[DescriptorBase]:
155
+ """
156
+ Get all objects which can be linked against as a list.
157
+
158
+ :return: List of `Descriptor`/`Parameter` objects.
159
+ """
160
+ item_list = []
161
+ for key, item in self._kwargs.items():
162
+ if hasattr(item, '_get_linkable_attributes'):
163
+ item_list = [*item_list, *item._get_linkable_attributes()]
164
+ elif issubclass(type(item), (DescriptorBase)):
165
+ item_list.append(item)
166
+ return item_list
167
+
168
+ def get_fit_parameters(self) -> List[Parameter]:
169
+ """
170
+ Get all objects which can be fitted (and are not fixed) as a list.
171
+
172
+ :return: List of `Parameter` objects which can be used in fitting.
173
+ """
174
+ fit_list = []
175
+ for key, item in self._kwargs.items():
176
+ if hasattr(item, 'get_fit_parameters'):
177
+ fit_list = [*fit_list, *item.get_fit_parameters()]
178
+ elif isinstance(item, Parameter):
179
+ if item.independent and not item.fixed:
180
+ fit_list.append(item)
181
+ return fit_list
182
+
183
+ def __dir__(self) -> Iterable[str]:
184
+ """
185
+ This creates auto-completion and helps out in iPython notebooks.
186
+
187
+ :return: list of function and parameter names for auto-completion
188
+ """
189
+ new_class_objs = list(k for k in dir(self.__class__) if not k.startswith('_'))
190
+ return sorted(new_class_objs)
191
+
192
+ def __copy__(self) -> BasedBase:
193
+ """Return a copy of the object."""
194
+ temp = self.as_dict(skip=['unique_name'])
195
+ new_obj = self.__class__.from_dict(temp)
196
+ return new_obj
197
+
198
+
@@ -1,12 +1,9 @@
1
- # SPDX-FileCopyrightText: 2023 EasyScience contributors <core@easyscience.software>
1
+ # SPDX-FileCopyrightText: 2025 EasyScience contributors <core@easyscience.software>
2
2
  # SPDX-License-Identifier: BSD-3-Clause
3
- # © 2021-2023 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
3
+ # © 2021-2025 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- __author__ = 'github.com/wardsimon'
8
- __version__ = '0.1.0'
9
-
10
7
  from collections.abc import MutableSequence
11
8
  from numbers import Number
12
9
  from typing import TYPE_CHECKING
@@ -15,32 +12,31 @@ from typing import Callable
15
12
  from typing import List
16
13
  from typing import Optional
17
14
  from typing import Tuple
18
- from typing import TypeVar
19
15
  from typing import Union
20
16
 
21
17
  from easyscience.global_object.undo_redo import NotarizedDict
22
- from easyscience.Objects.ObjectClasses import BasedBase
23
- from easyscience.Objects.variable.descriptor_base import DescriptorBase
18
+
19
+ from ..variable.descriptor_base import DescriptorBase
20
+ from .based_base import BasedBase
24
21
 
25
22
  if TYPE_CHECKING:
26
- from easyscience.Objects.Inferface import iF
27
- from easyscience.Objects.ObjectClasses import B
28
- V = TypeVar('V', bound=DescriptorBase)
23
+ from ..fitting.calculators import InterfaceFactoryTemplate
24
+
29
25
 
30
26
 
31
- class BaseCollection(BasedBase, MutableSequence):
27
+ class CollectionBase(BasedBase, MutableSequence):
32
28
  """
33
29
  This is the base class for which all higher level classes are built off of.
34
30
  NOTE: This object is serializable only if parameters are supplied as:
35
- `BaseObj(a=value, b=value)`. For `Parameter` or `Descriptor` objects we can
36
- cheat with `BaseObj(*[Descriptor(...), Parameter(...), ...])`.
31
+ `ObjBase(a=value, b=value)`. For `Parameter` or `Descriptor` objects we can
32
+ cheat with `ObjBase(*[Descriptor(...), Parameter(...), ...])`.
37
33
  """
38
34
 
39
35
  def __init__(
40
36
  self,
41
37
  name: str,
42
- *args: Union[B, V],
43
- interface: Optional[iF] = None,
38
+ *args: Union[BasedBase, DescriptorBase],
39
+ interface: Optional[InterfaceFactoryTemplate] = None,
44
40
  unique_name: Optional[str] = None,
45
41
  **kwargs,
46
42
  ):
@@ -95,7 +91,7 @@ class BaseCollection(BasedBase, MutableSequence):
95
91
  self.interface = interface
96
92
  self._kwargs._stack_enabled = True
97
93
 
98
- def insert(self, index: int, value: Union[V, B]) -> None:
94
+ def insert(self, index: int, value: Union[DescriptorBase, BasedBase]) -> None:
99
95
  """
100
96
  Insert an object into the collection at an index.
101
97
 
@@ -122,14 +118,14 @@ class BaseCollection(BasedBase, MutableSequence):
122
118
  else:
123
119
  raise AttributeError('Only EasyScience objects can be put into an EasyScience group')
124
120
 
125
- def __getitem__(self, idx: Union[int, slice]) -> Union[V, B]:
121
+ def __getitem__(self, idx: Union[int, slice]) -> Union[DescriptorBase, BasedBase]:
126
122
  """
127
123
  Get an item in the collection based on its index.
128
124
 
129
125
  :param idx: index or slice of the collection.
130
126
  :type idx: Union[int, slice]
131
127
  :return: Object at index `idx`
132
- :rtype: Union[Parameter, Descriptor, BaseObj, 'BaseCollection']
128
+ :rtype: Union[Parameter, Descriptor, ObjBase, 'CollectionBase']
133
129
  """
134
130
  if isinstance(idx, slice):
135
131
  start, stop, step = idx.indices(len(self))
@@ -156,7 +152,7 @@ class BaseCollection(BasedBase, MutableSequence):
156
152
  keys = list(self._kwargs.keys())
157
153
  return self._kwargs[keys[idx]]
158
154
 
159
- def __setitem__(self, key: int, value: Union[B, V]) -> None:
155
+ def __setitem__(self, key: int, value: Union[BasedBase, DescriptorBase]) -> None:
160
156
  """
161
157
  Set an item via it's index.
162
158
 
@@ -238,7 +234,7 @@ class BaseCollection(BasedBase, MutableSequence):
238
234
  def __repr__(self) -> str:
239
235
  return f"{self.__class__.__name__} `{getattr(self, 'name')}` of length {len(self)}"
240
236
 
241
- def sort(self, mapping: Callable[[Union[B, V]], Any], reverse: bool = False) -> None:
237
+ def sort(self, mapping: Callable[[Union[BasedBase, DescriptorBase]], Any], reverse: bool = False) -> None:
242
238
  """
243
239
  Sort the collection according to the given mapping.
244
240
 
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ # SPDX-FileCopyrightText: 2025 EasyScience contributors <core@easyscience.software>
4
+ # SPDX-License-Identifier: BSD-3-Clause
5
+ # © 2021-2025 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
6
+ from typing import TYPE_CHECKING
7
+ from typing import Callable
8
+ from typing import Optional
9
+
10
+ from ..utils.classTools import addLoggedProp
11
+ from ..variable.descriptor_base import DescriptorBase
12
+ from .based_base import BasedBase
13
+
14
+ if TYPE_CHECKING:
15
+ from ..io import SerializerComponent
16
+
17
+
18
+
19
+ class ObjBase(BasedBase):
20
+ """
21
+ This is the base class for which all higher level classes are built off of.
22
+ NOTE: This object is serializable only if parameters are supplied as:
23
+ `ObjBase(a=value, b=value)`. For `Parameter` or `Descriptor` objects we can
24
+ cheat with `ObjBase(*[Descriptor(...), Parameter(...), ...])`.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ name: str,
30
+ unique_name: Optional[str] = None,
31
+ *args: Optional[SerializerComponent],
32
+ **kwargs: Optional[SerializerComponent],
33
+ ):
34
+ """
35
+ Set up the base class.
36
+
37
+ :param name: Name of this object
38
+ :param args: Any arguments?
39
+ :param kwargs: Fields which this class should contain
40
+ """
41
+ super(ObjBase, self).__init__(name=name, unique_name=unique_name)
42
+ # If Parameter or Descriptor is given as arguments...
43
+ for arg in args:
44
+ if issubclass(type(arg), (ObjBase, DescriptorBase)):
45
+ kwargs[getattr(arg, 'name')] = arg
46
+ # Set kwargs, also useful for serialization
47
+ known_keys = self.__dict__.keys()
48
+ self._kwargs = kwargs
49
+ for key in kwargs.keys():
50
+ if key in known_keys:
51
+ raise AttributeError('Kwargs cannot overwrite class attributes in ObjBase.')
52
+ if issubclass(type(kwargs[key]), (BasedBase, DescriptorBase)) or 'CollectionBase' in [
53
+ c.__name__ for c in type(kwargs[key]).__bases__
54
+ ]:
55
+ self._global_object.map.add_edge(self, kwargs[key])
56
+ self._global_object.map.reset_type(kwargs[key], 'created_internal')
57
+ addLoggedProp(
58
+ self,
59
+ key,
60
+ self.__getter(key),
61
+ self.__setter(key),
62
+ get_id=key,
63
+ my_self=self,
64
+ test_class=ObjBase,
65
+ )
66
+
67
+ def _add_component(self, key: str, component: SerializerComponent) -> None:
68
+ """
69
+ Dynamically add a component to the class. This is an internal method, though can be called remotely.
70
+ The recommended alternative is to use typing, i.e.
71
+
72
+ class Foo(Bar):
73
+ def __init__(self, foo: Parameter, bar: Parameter):
74
+ super(Foo, self).__init__(bar=bar)
75
+ self._add_component("foo", foo)
76
+
77
+ Goes to:
78
+ class Foo(Bar):
79
+ foo: ClassVar[Parameter]
80
+ def __init__(self, foo: Parameter, bar: Parameter):
81
+ super(Foo, self).__init__(bar=bar)
82
+ self.foo = foo
83
+
84
+ :param key: Name of component to be added
85
+ :param component: Component to be added
86
+ :return: None
87
+ """
88
+ self._kwargs[key] = component
89
+ self._global_object.map.add_edge(self, component)
90
+ self._global_object.map.reset_type(component, 'created_internal')
91
+ addLoggedProp(
92
+ self,
93
+ key,
94
+ self.__getter(key),
95
+ self.__setter(key),
96
+ get_id=key,
97
+ my_self=self,
98
+ test_class=ObjBase,
99
+ )
100
+
101
+ def __setattr__(self, key: str, value: SerializerComponent) -> None:
102
+ # Assume that the annotation is a ClassVar
103
+ old_obj = None
104
+ if (
105
+ hasattr(self.__class__, '__annotations__')
106
+ and key in self.__class__.__annotations__
107
+ and hasattr(self.__class__.__annotations__[key], '__args__')
108
+ and issubclass(
109
+ getattr(value, '__old_class__', value.__class__),
110
+ self.__class__.__annotations__[key].__args__,
111
+ )
112
+ ):
113
+ if issubclass(type(getattr(self, key, None)), (BasedBase, DescriptorBase)):
114
+ old_obj = self.__getattribute__(key)
115
+ self._global_object.map.prune_vertex_from_edge(self, old_obj)
116
+ self._add_component(key, value)
117
+ else:
118
+ if hasattr(self, key) and issubclass(type(value), (BasedBase, DescriptorBase)):
119
+ old_obj = self.__getattribute__(key)
120
+ self._global_object.map.prune_vertex_from_edge(self, old_obj)
121
+ self._global_object.map.add_edge(self, value)
122
+ super(ObjBase, self).__setattr__(key, value)
123
+ # Update the interface bindings if something changed (BasedBase and Descriptor)
124
+ if old_obj is not None:
125
+ old_interface = getattr(self, 'interface', None)
126
+ if old_interface is not None:
127
+ self.generate_bindings()
128
+
129
+ def __repr__(self) -> str:
130
+ return f"{self.__class__.__name__} `{getattr(self, 'name')}`"
131
+
132
+ @staticmethod
133
+ def __getter(key: str) -> Callable[[SerializerComponent], SerializerComponent]:
134
+ def getter(obj: SerializerComponent) -> SerializerComponent:
135
+ return obj._kwargs[key]
136
+
137
+ return getter
138
+
139
+ @staticmethod
140
+ def __setter(key: str) -> Callable[[SerializerComponent], None]:
141
+ def setter(obj: SerializerComponent, value: float) -> None:
142
+ if issubclass(obj._kwargs[key].__class__, (DescriptorBase)) and not issubclass(
143
+ value.__class__, (DescriptorBase)
144
+ ):
145
+ obj._kwargs[key].value = value
146
+ else:
147
+ obj._kwargs[key] = value
148
+
149
+ return setter
150
+
151
+ # @staticmethod
152
+ # def __setter(key: str) -> Callable[[Union[B, V]], None]:
153
+ # def setter(obj: Union[V, B], value: float) -> None:
154
+ # if issubclass(obj._kwargs[key].__class__, Descriptor):
155
+ # if issubclass(obj._kwargs[key].__class__, Descriptor):
156
+ # obj._kwargs[key] = value
157
+ # else:
158
+ # obj._kwargs[key].value = value
159
+ # else:
160
+ # obj._kwargs[key] = value
161
+ #
162
+ # return setter
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2025 EasyScience contributors <core@easyscience.software>
2
+ # SPDX-License-Identifier: BSD-3-Clause
3
+ # © 2021-2025 Contributors to the EasyScience project <https://github.com/easyScience/easyscience
4
+
5
+ from .interface_factory import InterfaceFactoryTemplate
6
+
7
+ __all__ = [InterfaceFactoryTemplate]
@@ -1,25 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
- # SPDX-FileCopyrightText: 2023 EasyScience contributors <core@easyscience.software>
3
+ # SPDX-FileCopyrightText: 2025 EasyScience contributors <core@easyscience.software>
4
4
  # SPDX-License-Identifier: BSD-3-Clause
5
- # © 2021-2023 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
6
-
7
- __author__ = 'github.com/wardsimon'
8
- __version__ = '0.1.0'
9
-
10
- from abc import ABCMeta
5
+ # © 2021-2025 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
11
6
  from typing import TYPE_CHECKING
12
7
  from typing import Callable
13
8
  from typing import List
14
9
  from typing import NamedTuple
15
10
  from typing import Optional
16
11
  from typing import Type
17
- from typing import TypeVar
18
12
 
19
- _C = TypeVar('_C', bound=ABCMeta)
20
- _M = TypeVar('_M')
21
13
  if TYPE_CHECKING:
22
- from easyscience.fitting import Fitter
14
+ from abc import ABCMeta
15
+
16
+ from .. import Fitter
23
17
 
24
18
 
25
19
  class InterfaceFactoryTemplate:
@@ -27,10 +21,10 @@ class InterfaceFactoryTemplate:
27
21
  This class allows for the creation and transference of interfaces.
28
22
  """
29
23
 
30
- def __init__(self, interface_list: List[_C], *args, **kwargs):
31
- self._interfaces: List[_C] = interface_list
32
- self._current_interface: _C
33
- self.__interface_obj: _M = None
24
+ def __init__(self, interface_list: List[ABCMeta], *args, **kwargs):
25
+ self._interfaces: List[ABCMeta] = interface_list
26
+ self._current_interface: ABCMeta
27
+ self.__interface_obj: None = None
34
28
  self.create(*args, **kwargs)
35
29
 
36
30
  def create(self, *args, **kwargs):
@@ -60,7 +54,7 @@ class InterfaceFactoryTemplate:
60
54
  def switch(self, new_interface: str, fitter: Optional[Type[Fitter]] = None):
61
55
  """
62
56
  Changes the current interface to a new interface. The current interface is destroyed and
63
- all ComponentSerializer parameters carried over to the new interface. i.e. pick up where you left off.
57
+ all SerializerComponent parameters carried over to the new interface. i.e. pick up where you left off.
64
58
 
65
59
  :param new_interface: name of new interface to be created
66
60
  :type new_interface: str
@@ -100,7 +94,7 @@ class InterfaceFactoryTemplate:
100
94
  return [self.return_name(this_interface) for this_interface in self._interfaces]
101
95
 
102
96
  @property
103
- def current_interface(self) -> _C:
97
+ def current_interface(self) -> ABCMeta:
104
98
  """
105
99
  Returns the constructor for the currently selected interface.
106
100
 
@@ -174,7 +168,7 @@ class InterfaceFactoryTemplate:
174
168
  prop._callback = item.make_prop(item_key)
175
169
  prop._callback.fset(prop_value)
176
170
 
177
- def __call__(self, *args, **kwargs) -> _M:
171
+ def __call__(self, *args, **kwargs) -> None:
178
172
  return self.__interface_obj
179
173
 
180
174
  def __reduce__(self):
@@ -233,6 +227,3 @@ class ItemContainer(NamedTuple):
233
227
  self.setter_fn(self.link_name, **{inner_key: value})
234
228
 
235
229
  return set_value
236
-
237
-
238
- iF = TypeVar('iF', bound=InterfaceFactoryTemplate)