swarmauri-typing 0.7.0.dev2__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.
- swarmauri_typing-0.7.0.dev2/PKG-INFO +154 -0
- swarmauri_typing-0.7.0.dev2/README.md +139 -0
- swarmauri_typing-0.7.0.dev2/pyproject.toml +57 -0
- swarmauri_typing-0.7.0.dev2/swarmauri_typing/Intersection.py +47 -0
- swarmauri_typing-0.7.0.dev2/swarmauri_typing/UnionFactory.py +109 -0
- swarmauri_typing-0.7.0.dev2/swarmauri_typing/__init__.py +22 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: swarmauri-typing
|
|
3
|
+
Version: 0.7.0.dev2
|
|
4
|
+
Summary: This repository includes typing used in the Swarmauri framework.
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Author: Jacob Stewart
|
|
7
|
+
Author-email: jacob@swarmauri.com
|
|
8
|
+
Requires-Python: >=3.10,<3.13
|
|
9
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="https://pypi.org/project/swarmauri/">
|
|
19
|
+
<img src="https://img.shields.io/pypi/dm/swarmauri" alt="PyPI - Downloads"/></a>
|
|
20
|
+
<a href="https://github.com/swarmauri/swarmauri-sdk">
|
|
21
|
+
<img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://github.com/swarmauri/swarmauri-sdk&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false" alt="GitHub Hits"/></a>
|
|
22
|
+
<a href="https://pypi.org/project/swarmauri_typing/">
|
|
23
|
+
<img src="https://img.shields.io/pypi/pyversions/swarmauri" alt="PyPI - Python Version"/></a>
|
|
24
|
+
<a href="https://pypi.org/project/swarmauri_typing/">
|
|
25
|
+
<img src="https://img.shields.io/pypi/l/swarmauri_typing" alt="PyPI - License"/></a>
|
|
26
|
+
<br />
|
|
27
|
+
<a href="https://pypi.org/project/swarmauri/">
|
|
28
|
+
<img src="https://img.shields.io/pypi/v/swarmauri?label=swarmauri_typing&color=green" alt="PyPI - swarmauri_core"/></a>
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Swarmauri Typing Library
|
|
33
|
+
|
|
34
|
+
The Swarmauri Typing Library provides advanced type utilities for Python, enabling more expressive and flexible type annotations. It includes tools for creating intersection and union types dynamically.
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- **Intersection Types**: Create intersection types that combine multiple classes.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from typing import Type, TypeVar, Union, Any, Annotated, Tuple
|
|
42
|
+
|
|
43
|
+
T = TypeVar("T")
|
|
44
|
+
|
|
45
|
+
class IntersectionMetadata:
|
|
46
|
+
def __init__(self, classes: Tuple[Type[T]]):
|
|
47
|
+
self.classes = classes
|
|
48
|
+
|
|
49
|
+
def __repr__(self):
|
|
50
|
+
return f"IntersectionMetadata(classes={self.classes!r})"
|
|
51
|
+
|
|
52
|
+
class Intersection(type):
|
|
53
|
+
def __class_getitem__(cls, classes: Union[Type, Tuple[Type, ...]]) -> type:
|
|
54
|
+
if not isinstance(classes, tuple):
|
|
55
|
+
classes = (classes,)
|
|
56
|
+
|
|
57
|
+
common = set(classes[0].__mro__)
|
|
58
|
+
for c in classes[1:]:
|
|
59
|
+
common.intersection_update(c.__mro__)
|
|
60
|
+
|
|
61
|
+
ordered_common = [c for c in classes[0].__mro__ if c in common]
|
|
62
|
+
|
|
63
|
+
if not ordered_common:
|
|
64
|
+
return Annotated[Any, IntersectionMetadata(classes=(classes))]
|
|
65
|
+
else:
|
|
66
|
+
union_type = Union[tuple(ordered_common)]
|
|
67
|
+
return Annotated[union_type, IntersectionMetadata(classes=(classes))]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **Union Factory**: Dynamically create union types based on a provided function.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from typing import Type, TypeVar, Callable, List, Union, Any, Annotated, get_args, Optional
|
|
74
|
+
|
|
75
|
+
T = TypeVar("T")
|
|
76
|
+
|
|
77
|
+
class UnionFactoryMetadata:
|
|
78
|
+
def __init__(self, data: Any, name: Optional[str] = None):
|
|
79
|
+
self.data = data
|
|
80
|
+
self.name = name or self.__class__.__name__
|
|
81
|
+
|
|
82
|
+
def __repr__(self):
|
|
83
|
+
return f"UnionFactoryMetadata(name={self.name!r}, data={self.data!r})"
|
|
84
|
+
|
|
85
|
+
class UnionFactory:
|
|
86
|
+
def __init__(self, bound: Callable[[Type[T]], List[type]], name: str = None, annotation_extenders: List[Any] = None):
|
|
87
|
+
self.name = name or self.__class__.__name__
|
|
88
|
+
self._union_types_getter = bound
|
|
89
|
+
self._annotation_extenders = annotation_extenders or []
|
|
90
|
+
|
|
91
|
+
def _add_metadata(self, annotated_type: Any, new_metadata: Any) -> Any:
|
|
92
|
+
if not (hasattr(annotated_type, '__origin__') and annotated_type.__origin__ is Annotated):
|
|
93
|
+
return Annotated[annotated_type, new_metadata]
|
|
94
|
+
|
|
95
|
+
args = get_args(annotated_type)
|
|
96
|
+
base_type = args[0]
|
|
97
|
+
old_metadata = args[1:]
|
|
98
|
+
return Annotated[base_type, *old_metadata, new_metadata]
|
|
99
|
+
|
|
100
|
+
def __getitem__(self, input_data: Union[Type[T], str]) -> type:
|
|
101
|
+
if isinstance(input_data, str):
|
|
102
|
+
model_name = input_data
|
|
103
|
+
else:
|
|
104
|
+
model_name = input_data.__name__
|
|
105
|
+
|
|
106
|
+
union_members = self._union_types_getter(model_name)
|
|
107
|
+
|
|
108
|
+
if not union_members:
|
|
109
|
+
final_annotated = Annotated[Any, UnionFactoryMetadata(data=model_name, name=self.name)]
|
|
110
|
+
else:
|
|
111
|
+
union_type = Union[tuple(union_members)]
|
|
112
|
+
final_annotated = Annotated[union_type, UnionFactoryMetadata(data=model_name, name=self.name)]
|
|
113
|
+
|
|
114
|
+
for extension in self._annotation_extenders:
|
|
115
|
+
final_annotated = self._add_metadata(final_annotated, extension)
|
|
116
|
+
|
|
117
|
+
return final_annotated
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Getting Started
|
|
121
|
+
|
|
122
|
+
To start using the Swarmauri Typing Library, include it as a module in your Python project. Ensure you have Python 3.10 or later installed.
|
|
123
|
+
|
|
124
|
+
### Steps to install via pypi
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
pip install swarmauri-typing
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Usage Example
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from swarmauri_typing import Intersection, UnionFactory
|
|
134
|
+
|
|
135
|
+
# Example of using Intersection
|
|
136
|
+
class A: pass
|
|
137
|
+
class B: pass
|
|
138
|
+
|
|
139
|
+
IntersectionType = Intersection[A, B]
|
|
140
|
+
|
|
141
|
+
# Example of using UnionFactory
|
|
142
|
+
def my_types_getter(name: str):
|
|
143
|
+
return [A, B]
|
|
144
|
+
|
|
145
|
+
union_factory = UnionFactory(my_types_getter)
|
|
146
|
+
MyUnion = union_factory["MyModel"]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
## Contributing
|
|
151
|
+
|
|
152
|
+
Contributions are welcome! If you'd like to add a new feature, fix a bug, or improve documentation, kindly go through the [contributions guidelines](https://github.com/swarmauri/swarmauri-sdk/blob/master/contributing.md) first.
|
|
153
|
+
|
|
154
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://pypi.org/project/swarmauri/">
|
|
5
|
+
<img src="https://img.shields.io/pypi/dm/swarmauri" alt="PyPI - Downloads"/></a>
|
|
6
|
+
<a href="https://github.com/swarmauri/swarmauri-sdk">
|
|
7
|
+
<img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://github.com/swarmauri/swarmauri-sdk&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false" alt="GitHub Hits"/></a>
|
|
8
|
+
<a href="https://pypi.org/project/swarmauri_typing/">
|
|
9
|
+
<img src="https://img.shields.io/pypi/pyversions/swarmauri" alt="PyPI - Python Version"/></a>
|
|
10
|
+
<a href="https://pypi.org/project/swarmauri_typing/">
|
|
11
|
+
<img src="https://img.shields.io/pypi/l/swarmauri_typing" alt="PyPI - License"/></a>
|
|
12
|
+
<br />
|
|
13
|
+
<a href="https://pypi.org/project/swarmauri/">
|
|
14
|
+
<img src="https://img.shields.io/pypi/v/swarmauri?label=swarmauri_typing&color=green" alt="PyPI - swarmauri_core"/></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Swarmauri Typing Library
|
|
19
|
+
|
|
20
|
+
The Swarmauri Typing Library provides advanced type utilities for Python, enabling more expressive and flexible type annotations. It includes tools for creating intersection and union types dynamically.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Intersection Types**: Create intersection types that combine multiple classes.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from typing import Type, TypeVar, Union, Any, Annotated, Tuple
|
|
28
|
+
|
|
29
|
+
T = TypeVar("T")
|
|
30
|
+
|
|
31
|
+
class IntersectionMetadata:
|
|
32
|
+
def __init__(self, classes: Tuple[Type[T]]):
|
|
33
|
+
self.classes = classes
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return f"IntersectionMetadata(classes={self.classes!r})"
|
|
37
|
+
|
|
38
|
+
class Intersection(type):
|
|
39
|
+
def __class_getitem__(cls, classes: Union[Type, Tuple[Type, ...]]) -> type:
|
|
40
|
+
if not isinstance(classes, tuple):
|
|
41
|
+
classes = (classes,)
|
|
42
|
+
|
|
43
|
+
common = set(classes[0].__mro__)
|
|
44
|
+
for c in classes[1:]:
|
|
45
|
+
common.intersection_update(c.__mro__)
|
|
46
|
+
|
|
47
|
+
ordered_common = [c for c in classes[0].__mro__ if c in common]
|
|
48
|
+
|
|
49
|
+
if not ordered_common:
|
|
50
|
+
return Annotated[Any, IntersectionMetadata(classes=(classes))]
|
|
51
|
+
else:
|
|
52
|
+
union_type = Union[tuple(ordered_common)]
|
|
53
|
+
return Annotated[union_type, IntersectionMetadata(classes=(classes))]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- **Union Factory**: Dynamically create union types based on a provided function.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from typing import Type, TypeVar, Callable, List, Union, Any, Annotated, get_args, Optional
|
|
60
|
+
|
|
61
|
+
T = TypeVar("T")
|
|
62
|
+
|
|
63
|
+
class UnionFactoryMetadata:
|
|
64
|
+
def __init__(self, data: Any, name: Optional[str] = None):
|
|
65
|
+
self.data = data
|
|
66
|
+
self.name = name or self.__class__.__name__
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
return f"UnionFactoryMetadata(name={self.name!r}, data={self.data!r})"
|
|
70
|
+
|
|
71
|
+
class UnionFactory:
|
|
72
|
+
def __init__(self, bound: Callable[[Type[T]], List[type]], name: str = None, annotation_extenders: List[Any] = None):
|
|
73
|
+
self.name = name or self.__class__.__name__
|
|
74
|
+
self._union_types_getter = bound
|
|
75
|
+
self._annotation_extenders = annotation_extenders or []
|
|
76
|
+
|
|
77
|
+
def _add_metadata(self, annotated_type: Any, new_metadata: Any) -> Any:
|
|
78
|
+
if not (hasattr(annotated_type, '__origin__') and annotated_type.__origin__ is Annotated):
|
|
79
|
+
return Annotated[annotated_type, new_metadata]
|
|
80
|
+
|
|
81
|
+
args = get_args(annotated_type)
|
|
82
|
+
base_type = args[0]
|
|
83
|
+
old_metadata = args[1:]
|
|
84
|
+
return Annotated[base_type, *old_metadata, new_metadata]
|
|
85
|
+
|
|
86
|
+
def __getitem__(self, input_data: Union[Type[T], str]) -> type:
|
|
87
|
+
if isinstance(input_data, str):
|
|
88
|
+
model_name = input_data
|
|
89
|
+
else:
|
|
90
|
+
model_name = input_data.__name__
|
|
91
|
+
|
|
92
|
+
union_members = self._union_types_getter(model_name)
|
|
93
|
+
|
|
94
|
+
if not union_members:
|
|
95
|
+
final_annotated = Annotated[Any, UnionFactoryMetadata(data=model_name, name=self.name)]
|
|
96
|
+
else:
|
|
97
|
+
union_type = Union[tuple(union_members)]
|
|
98
|
+
final_annotated = Annotated[union_type, UnionFactoryMetadata(data=model_name, name=self.name)]
|
|
99
|
+
|
|
100
|
+
for extension in self._annotation_extenders:
|
|
101
|
+
final_annotated = self._add_metadata(final_annotated, extension)
|
|
102
|
+
|
|
103
|
+
return final_annotated
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Getting Started
|
|
107
|
+
|
|
108
|
+
To start using the Swarmauri Typing Library, include it as a module in your Python project. Ensure you have Python 3.10 or later installed.
|
|
109
|
+
|
|
110
|
+
### Steps to install via pypi
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
pip install swarmauri-typing
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Usage Example
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from swarmauri_typing import Intersection, UnionFactory
|
|
120
|
+
|
|
121
|
+
# Example of using Intersection
|
|
122
|
+
class A: pass
|
|
123
|
+
class B: pass
|
|
124
|
+
|
|
125
|
+
IntersectionType = Intersection[A, B]
|
|
126
|
+
|
|
127
|
+
# Example of using UnionFactory
|
|
128
|
+
def my_types_getter(name: str):
|
|
129
|
+
return [A, B]
|
|
130
|
+
|
|
131
|
+
union_factory = UnionFactory(my_types_getter)
|
|
132
|
+
MyUnion = union_factory["MyModel"]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
## Contributing
|
|
137
|
+
|
|
138
|
+
Contributions are welcome! If you'd like to add a new feature, fix a bug, or improve documentation, kindly go through the [contributions guidelines](https://github.com/swarmauri/swarmauri-sdk/blob/master/contributing.md) first.
|
|
139
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "swarmauri-typing"
|
|
3
|
+
version = "0.7.0.dev2"
|
|
4
|
+
description = "This repository includes typing used in the Swarmauri framework."
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Jacob Stewart", email = "jacob@swarmauri.com" },
|
|
7
|
+
]
|
|
8
|
+
license = "Apache-2.0"
|
|
9
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
|
+
repository = "http://github.com/swarmauri/swarmauri-sdk"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"License :: OSI Approved :: Apache Software License",
|
|
13
|
+
"Programming Language :: Python :: 3.10",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12"
|
|
16
|
+
]
|
|
17
|
+
requires-python = ">=3.10,<3.13"
|
|
18
|
+
|
|
19
|
+
[tool.uv.sources]
|
|
20
|
+
swarmauri_core = { workspace = true }
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"toml>=0.10.2",
|
|
25
|
+
"pytest>=8.0.0",
|
|
26
|
+
"pytest-xdist>=3.6.1",
|
|
27
|
+
"pytest-asyncio>=0.24.0",
|
|
28
|
+
"pytest-timeout>=2.3.1",
|
|
29
|
+
"pytest-json-report>=1.5.0",
|
|
30
|
+
"python-dotenv>=1.0.0",
|
|
31
|
+
"pytest-mock>=3.14.0",
|
|
32
|
+
"jsonschema>=4.18.5",
|
|
33
|
+
"ruff>=0.9.9",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
markers = [
|
|
38
|
+
"test: standard test",
|
|
39
|
+
"unit: Unit tests",
|
|
40
|
+
"i9n: Integration Tests",
|
|
41
|
+
"r8n: Regression Tests",
|
|
42
|
+
"timeout: mark test to timeout after X seconds",
|
|
43
|
+
"xfail: Expected failures",
|
|
44
|
+
"xpass: Expected passes",
|
|
45
|
+
"acceptance: Acceptance tests"
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
log_cli = true
|
|
49
|
+
log_cli_level = "INFO"
|
|
50
|
+
log_cli_format = "%(asctime)s [%(levelname)s] %(message)s"
|
|
51
|
+
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
|
|
52
|
+
|
|
53
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
54
|
+
|
|
55
|
+
[build-system]
|
|
56
|
+
requires = ["poetry-core>=1.0.0"]
|
|
57
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Type, TypeVar, Union, Any, Annotated, Tuple
|
|
2
|
+
|
|
3
|
+
T = TypeVar("T")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IntersectionMetadata:
|
|
7
|
+
def __init__(self, classes: Tuple[Type[T]]):
|
|
8
|
+
self.classes = classes
|
|
9
|
+
|
|
10
|
+
def __repr__(self):
|
|
11
|
+
# Return a more 'developer-focused' string, e.g.:
|
|
12
|
+
return f"IntersectionMetadata(classes={self.classes!r})"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# The Intersection metaclass as provided.
|
|
16
|
+
class Intersection(type):
|
|
17
|
+
"""
|
|
18
|
+
A generic metaclass to create an intersection of discriminated subclasses.
|
|
19
|
+
Usage:
|
|
20
|
+
Intersection[TypeA, TypeB, ...]
|
|
21
|
+
will return an Annotated Union of all registered classes that are common to
|
|
22
|
+
all given resource types.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __class_getitem__(cls, classes: Union[Type, Tuple[Type, ...]]) -> type:
|
|
26
|
+
# Allow a single type or a tuple of types.
|
|
27
|
+
if not isinstance(classes, tuple):
|
|
28
|
+
classes = (classes,)
|
|
29
|
+
|
|
30
|
+
# Compute the intersection of all MRO sets.
|
|
31
|
+
common = set(classes[0].__mro__)
|
|
32
|
+
for c in classes[1:]:
|
|
33
|
+
common.intersection_update(c.__mro__)
|
|
34
|
+
|
|
35
|
+
# Order the common classes as they appear in the first class's MRO.
|
|
36
|
+
ordered_common = [c for c in classes[0].__mro__ if c in common]
|
|
37
|
+
|
|
38
|
+
if not ordered_common:
|
|
39
|
+
# Fallback to Any (should not happen as 'object' is always common)
|
|
40
|
+
return Annotated[Any, IntersectionMetadata(classes=(classes))]
|
|
41
|
+
else:
|
|
42
|
+
# Construct a Union type from the ordered common bases.
|
|
43
|
+
union_type = Union[tuple(ordered_common)]
|
|
44
|
+
return Annotated[
|
|
45
|
+
union_type,
|
|
46
|
+
IntersectionMetadata(classes=(classes)),
|
|
47
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
Type,
|
|
3
|
+
TypeVar,
|
|
4
|
+
Callable,
|
|
5
|
+
List,
|
|
6
|
+
Union,
|
|
7
|
+
Any,
|
|
8
|
+
Annotated,
|
|
9
|
+
get_args,
|
|
10
|
+
Optional,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UnionFactoryMetadata:
|
|
17
|
+
"""
|
|
18
|
+
Stores any metadata related to the union created by 'UnionFactory'.
|
|
19
|
+
You can store anything you like in here (e.g., the original input
|
|
20
|
+
or other context data).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, data: Any, name: Optional[str] = None):
|
|
24
|
+
self.data = data
|
|
25
|
+
self.name = name or self.__class__.__name__
|
|
26
|
+
|
|
27
|
+
def __repr__(self):
|
|
28
|
+
# Return a more 'developer-focused' string, e.g.:
|
|
29
|
+
return f"UnionFactoryMetadata(name={self.name!r}, data={self.data!r})"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class UnionFactory:
|
|
33
|
+
"""
|
|
34
|
+
A configurable factory for creating Annotated Union types.
|
|
35
|
+
|
|
36
|
+
This can be used for many scenarios—e.g., uniting a set of classes
|
|
37
|
+
discovered dynamically, or enumerating a set of allowed types
|
|
38
|
+
based on a function you supply.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
bound: Callable[[Type[T]], List[type]],
|
|
44
|
+
name: "str" = None,
|
|
45
|
+
annotation_extenders: List[Any] = None,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
:param bound:
|
|
49
|
+
A function that takes an input (like a parent class or any other
|
|
50
|
+
context) and returns a list of types to be included in the union.
|
|
51
|
+
:param annotation_extenders:
|
|
52
|
+
A list of metadata items to be appended (in order) to the final
|
|
53
|
+
Annotated[...] union (beyond the default UnionFactoryMetadata).
|
|
54
|
+
"""
|
|
55
|
+
self.name = name or self.__class__.__name__
|
|
56
|
+
self._union_types_getter = bound
|
|
57
|
+
self._annotation_extenders = annotation_extenders or []
|
|
58
|
+
|
|
59
|
+
def _add_metadata(self, annotated_type: Any, new_metadata: Any) -> Any:
|
|
60
|
+
"""
|
|
61
|
+
Appends 'new_metadata' to an existing Annotated type,
|
|
62
|
+
or wraps a bare type in Annotated if it's not already Annotated.
|
|
63
|
+
"""
|
|
64
|
+
if not (
|
|
65
|
+
hasattr(annotated_type, "__origin__")
|
|
66
|
+
and annotated_type.__origin__ is Annotated
|
|
67
|
+
):
|
|
68
|
+
return Annotated[annotated_type, new_metadata]
|
|
69
|
+
|
|
70
|
+
args = get_args(annotated_type)
|
|
71
|
+
base_type = args[0]
|
|
72
|
+
old_metadata = args[1:]
|
|
73
|
+
return Annotated[base_type, *old_metadata, new_metadata]
|
|
74
|
+
|
|
75
|
+
def __getitem__(self, input_data: Union[Type[T], str]) -> type:
|
|
76
|
+
"""
|
|
77
|
+
Usage example:
|
|
78
|
+
union_factory = UnionFactory(my_types_getter, [meta1, meta2])
|
|
79
|
+
MyUnion = union_factory[MyParentClassOrOtherInput]
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
An Annotated[...] union (or Annotated[Any, ...]) with:
|
|
83
|
+
• A UnionFactoryMetadata object referencing 'input_data'
|
|
84
|
+
• Any additional metadata from 'annotation_extenders'
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
if isinstance(input_data, str):
|
|
88
|
+
model_name = input_data
|
|
89
|
+
else:
|
|
90
|
+
model_name = input_data.__name__
|
|
91
|
+
|
|
92
|
+
union_members = self._union_types_getter(model_name)
|
|
93
|
+
|
|
94
|
+
# If no types are returned, fall back to Annotated[Any, UnionFactoryMetadata]
|
|
95
|
+
if not union_members:
|
|
96
|
+
final_annotated = Annotated[
|
|
97
|
+
Any, UnionFactoryMetadata(data=model_name, name=self.name)
|
|
98
|
+
]
|
|
99
|
+
else:
|
|
100
|
+
union_type = Union[tuple(union_members)]
|
|
101
|
+
final_annotated = Annotated[
|
|
102
|
+
union_type, UnionFactoryMetadata(data=model_name, name=self.name)
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# Add any additional metadata
|
|
106
|
+
for extension in self._annotation_extenders:
|
|
107
|
+
final_annotated = self._add_metadata(final_annotated, extension)
|
|
108
|
+
|
|
109
|
+
return final_annotated
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from .UnionFactory import UnionFactory, UnionFactoryMetadata
|
|
2
|
+
from .Intersection import Intersection, IntersectionMetadata
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"UnionFactory",
|
|
6
|
+
"UnionFactoryMetadata",
|
|
7
|
+
"Intersection",
|
|
8
|
+
"IntersectionMetadata",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
# For Python 3.8 and newer
|
|
13
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
14
|
+
except ImportError:
|
|
15
|
+
# For older Python versions, use the backport
|
|
16
|
+
from importlib_metadata import version, PackageNotFoundError
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
__version__ = version("swarmauri_typing")
|
|
20
|
+
except PackageNotFoundError:
|
|
21
|
+
# If the package is not installed (for example, during development)
|
|
22
|
+
__version__ = "0.0.0"
|