lalamo 0.3.1__tar.gz → 0.3.2__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.
- {lalamo-0.3.1 → lalamo-0.3.2}/PKG-INFO +3 -2
- {lalamo-0.3.1 → lalamo-0.3.2}/README.md +2 -1
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/__init__.py +1 -1
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/common.py +2 -1
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/common.py +42 -0
- lalamo-0.3.2/lalamo/registry_abc.py +63 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo.egg-info/PKG-INFO +3 -2
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo.egg-info/SOURCES.txt +4 -1
- lalamo-0.3.2/tests/test_model_spec.py +52 -0
- lalamo-0.3.2/tests/test_registry_abc.py +148 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/LICENSE +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/common.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/language_model.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/main.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/message_processor.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/__init__.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/common.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/__init__.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/executorch.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/huggingface/__init__.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/huggingface/common.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/huggingface/gemma2.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/huggingface/gemma3.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/huggingface/llama.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/huggingface/mistral.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/huggingface/qwen2.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/decoder_configs/huggingface/qwen3.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/huggingface_generation_config.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/huggingface_tokenizer_config.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/loaders/__init__.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/loaders/common.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/loaders/executorch.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/loaders/huggingface.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/__init__.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/deepseek.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/gemma.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/huggingface.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/llama.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/mistral.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/pleias.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/polaris.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/qwen.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/model_import/model_specs/reka.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/__init__.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/activations.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/attention.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/common.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/decoder.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/decoder_layer.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/embedding.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/kv_cache.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/linear.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/mlp.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/normalization.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/rope.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/torch_interop.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/modules/utils.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/quantization.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/sampling.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo/utils.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo.egg-info/dependency_links.txt +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo.egg-info/entry_points.txt +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo.egg-info/requires.txt +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/lalamo.egg-info/top_level.txt +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/pyproject.toml +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/setup.cfg +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/tests/test_generation.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/tests/test_huggingface_models.py +0 -0
- {lalamo-0.3.1 → lalamo-0.3.2}/tests/test_parameter_tree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lalamo
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: JAX library for optimization and export of models for use with the UZU inference engine.
|
|
5
5
|
Requires-Python: <4,>=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -32,7 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|
<a href="https://artifacts.trymirai.com/social/about_us.mp3"><img src="https://img.shields.io/badge/Listen-Podcast-red" alt="Listen to our podcast"></a>
|
|
33
33
|
<a href="https://docsend.com/v/76bpr/mirai2025"><img src="https://img.shields.io/badge/View-Deck-red" alt="View our deck"></a>
|
|
34
34
|
<a href="mailto:alexey@getmirai.co,dima@getmirai.co,aleksei@getmirai.co?subject=Interested%20in%20Mirai"><img src="https://img.shields.io/badge/Send-Email-green" alt="Contact us"></a>
|
|
35
|
-
<a href="https://docs.trymirai.com/
|
|
35
|
+
<a href="https://docs.trymirai.com/overview/lalamo"><img src="https://img.shields.io/badge/Read-Docs-blue" alt="Read docs"></a>
|
|
36
36
|
[](LICENSE)
|
|
37
37
|
|
|
38
38
|
# lalamo
|
|
@@ -73,3 +73,4 @@ ModelSpec(
|
|
|
73
73
|
weights_type=WeightsType.SAFETENSORS,
|
|
74
74
|
)
|
|
75
75
|
```
|
|
76
|
+
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<a href="https://artifacts.trymirai.com/social/about_us.mp3"><img src="https://img.shields.io/badge/Listen-Podcast-red" alt="Listen to our podcast"></a>
|
|
8
8
|
<a href="https://docsend.com/v/76bpr/mirai2025"><img src="https://img.shields.io/badge/View-Deck-red" alt="View our deck"></a>
|
|
9
9
|
<a href="mailto:alexey@getmirai.co,dima@getmirai.co,aleksei@getmirai.co?subject=Interested%20in%20Mirai"><img src="https://img.shields.io/badge/Send-Email-green" alt="Contact us"></a>
|
|
10
|
-
<a href="https://docs.trymirai.com/
|
|
10
|
+
<a href="https://docs.trymirai.com/overview/lalamo"><img src="https://img.shields.io/badge/Read-Docs-blue" alt="Read docs"></a>
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
|
|
13
13
|
# lalamo
|
|
@@ -48,3 +48,4 @@ ModelSpec(
|
|
|
48
48
|
weights_type=WeightsType.SAFETENSORS,
|
|
49
49
|
)
|
|
50
50
|
```
|
|
51
|
+
|
|
@@ -9,12 +9,13 @@ import cattrs
|
|
|
9
9
|
from jaxtyping import Array, DTypeLike
|
|
10
10
|
|
|
11
11
|
from lalamo.modules import Decoder, DecoderConfig
|
|
12
|
+
from lalamo.registry_abc import RegistryABC
|
|
12
13
|
|
|
13
14
|
__all__ = ["ForeignConfig"]
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@dataclass(frozen=True)
|
|
17
|
-
class ForeignConfig:
|
|
18
|
+
class ForeignConfig(RegistryABC):
|
|
18
19
|
_converter: ClassVar[cattrs.Converter] = cattrs.Converter()
|
|
19
20
|
_converter.register_structure_hook(int | list[int], lambda v, _: v)
|
|
20
21
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from collections.abc import (
|
|
2
|
+
Callable,
|
|
2
3
|
Mapping,
|
|
3
4
|
)
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
from enum import Enum
|
|
6
7
|
from pathlib import Path
|
|
8
|
+
from typing import ClassVar, cast, get_args, get_origin
|
|
7
9
|
|
|
10
|
+
import cattrs
|
|
8
11
|
import jax.numpy as jnp
|
|
9
12
|
from jaxtyping import Array, DTypeLike
|
|
10
13
|
from safetensors.flax import load_file as load_safetensors
|
|
@@ -65,8 +68,40 @@ class ConfigMap:
|
|
|
65
68
|
chat_template: FileSpec | None = None
|
|
66
69
|
|
|
67
70
|
|
|
71
|
+
def _is_foreign_config_type(t: object) -> bool:
|
|
72
|
+
origin = get_origin(t)
|
|
73
|
+
args = get_args(t)
|
|
74
|
+
return origin is type and len(args) == 1 and isinstance(args[0], type) and issubclass(args[0], ForeignConfig)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _structure_foreign_config_factory(
|
|
78
|
+
t: object,
|
|
79
|
+
c: cattrs.Converter,
|
|
80
|
+
) -> Callable[[object, object], type[ForeignConfig]]:
|
|
81
|
+
name_to_type = {t.__name__: t for t in ForeignConfig.__descendants__()}
|
|
82
|
+
|
|
83
|
+
def _hook(v: object, _t: object) -> type[ForeignConfig]:
|
|
84
|
+
if isinstance(v, type) and issubclass(v, ForeignConfig):
|
|
85
|
+
return v
|
|
86
|
+
return name_to_type[cast("str", v)]
|
|
87
|
+
|
|
88
|
+
return _hook
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _unstructure_foreign_config_factory(t: object, c: cattrs.Converter) -> Callable[[type[ForeignConfig]], str]:
|
|
92
|
+
def _hook(v: type[ForeignConfig]) -> str:
|
|
93
|
+
return v.__name__
|
|
94
|
+
|
|
95
|
+
return _hook
|
|
96
|
+
|
|
97
|
+
|
|
68
98
|
@dataclass(frozen=True)
|
|
69
99
|
class ModelSpec:
|
|
100
|
+
_converter: ClassVar[cattrs.Converter] = cattrs.Converter()
|
|
101
|
+
|
|
102
|
+
_converter.register_structure_hook_factory(_is_foreign_config_type, _structure_foreign_config_factory)
|
|
103
|
+
_converter.register_unstructure_hook_factory(_is_foreign_config_type, _unstructure_foreign_config_factory)
|
|
104
|
+
|
|
70
105
|
vendor: str
|
|
71
106
|
family: str
|
|
72
107
|
name: str
|
|
@@ -83,6 +118,13 @@ class ModelSpec:
|
|
|
83
118
|
configs: ConfigMap = field(default=ConfigMap())
|
|
84
119
|
use_cases: tuple[UseCase, ...] = tuple()
|
|
85
120
|
|
|
121
|
+
@classmethod
|
|
122
|
+
def from_json(cls, json_data: dict) -> "ModelSpec":
|
|
123
|
+
return cls._converter.structure(json_data, cls)
|
|
124
|
+
|
|
125
|
+
def to_json(self) -> dict:
|
|
126
|
+
return self._converter.unstructure(self)
|
|
127
|
+
|
|
86
128
|
|
|
87
129
|
def awq_model_spec(
|
|
88
130
|
model_spec: ModelSpec,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from abc import ABC, ABCMeta
|
|
2
|
+
from typing import Any
|
|
3
|
+
from weakref import WeakSet
|
|
4
|
+
|
|
5
|
+
__all__ = ["RegistryABC", "RegistryMeta"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RegistryMeta(ABCMeta):
|
|
9
|
+
"""
|
|
10
|
+
Metaclass that tracks, for each subclass of RegistryABC, a per-class WeakSet
|
|
11
|
+
of descendants (classes that have it in their MRO) while excluding any class
|
|
12
|
+
that directly lists `RegistryABC` among its bases.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_REG_ATTR: str = "__registry_descendants__"
|
|
16
|
+
_ROOT: type["RegistryABC"] | None = None
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
cls: type,
|
|
20
|
+
name: str,
|
|
21
|
+
bases: tuple[type, ...],
|
|
22
|
+
namespace: dict[str, object],
|
|
23
|
+
**kwargs: Any, # noqa: ANN401
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(name, bases, namespace, **kwargs) # type: ignore[call-overload]
|
|
26
|
+
|
|
27
|
+
# Give *every* class its own WeakSet (shadow any inherited attribute)
|
|
28
|
+
setattr(cls, RegistryMeta._REG_ATTR, WeakSet())
|
|
29
|
+
|
|
30
|
+
# Detect and remember the root exactly once
|
|
31
|
+
if RegistryMeta._ROOT is None and name == "RegistryABC":
|
|
32
|
+
RegistryMeta._ROOT = cls # type: ignore[assignment]
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
root = RegistryMeta._ROOT
|
|
36
|
+
if root is None:
|
|
37
|
+
# Extremely early import edge-case; nothing to register yet
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
# Exclude classes that directly list the root among bases
|
|
41
|
+
if any(b is root for b in cls.__bases__):
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Register this class on all qualifying ancestors below the root
|
|
45
|
+
for ancestor in cls.__mro__[1:]:
|
|
46
|
+
if isinstance(ancestor, RegistryMeta) and issubclass(ancestor, root):
|
|
47
|
+
getattr(ancestor, RegistryMeta._REG_ATTR).add(cls)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RegistryABC(ABC, metaclass=RegistryMeta):
|
|
51
|
+
"""
|
|
52
|
+
Abstract base tracked by RegistryMeta.
|
|
53
|
+
|
|
54
|
+
Any class defined as `class AbstractFoo(RegistryABC)` will expose a
|
|
55
|
+
class method `AbstractFoo.__get_descendants__()` that returns a list of
|
|
56
|
+
all classes having AbstractFoo in their MRO *except* those that directly
|
|
57
|
+
include `RegistryABC` among their bases.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def __descendants__(cls) -> tuple[type, ...]:
|
|
62
|
+
reg: WeakSet[type] = getattr(cls, RegistryMeta._REG_ATTR) # noqa: SLF001
|
|
63
|
+
return tuple(reg)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lalamo
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: JAX library for optimization and export of models for use with the UZU inference engine.
|
|
5
5
|
Requires-Python: <4,>=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -32,7 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|
<a href="https://artifacts.trymirai.com/social/about_us.mp3"><img src="https://img.shields.io/badge/Listen-Podcast-red" alt="Listen to our podcast"></a>
|
|
33
33
|
<a href="https://docsend.com/v/76bpr/mirai2025"><img src="https://img.shields.io/badge/View-Deck-red" alt="View our deck"></a>
|
|
34
34
|
<a href="mailto:alexey@getmirai.co,dima@getmirai.co,aleksei@getmirai.co?subject=Interested%20in%20Mirai"><img src="https://img.shields.io/badge/Send-Email-green" alt="Contact us"></a>
|
|
35
|
-
<a href="https://docs.trymirai.com/
|
|
35
|
+
<a href="https://docs.trymirai.com/overview/lalamo"><img src="https://img.shields.io/badge/Read-Docs-blue" alt="Read docs"></a>
|
|
36
36
|
[](LICENSE)
|
|
37
37
|
|
|
38
38
|
# lalamo
|
|
@@ -73,3 +73,4 @@ ModelSpec(
|
|
|
73
73
|
weights_type=WeightsType.SAFETENSORS,
|
|
74
74
|
)
|
|
75
75
|
```
|
|
76
|
+
|
|
@@ -7,6 +7,7 @@ lalamo/language_model.py
|
|
|
7
7
|
lalamo/main.py
|
|
8
8
|
lalamo/message_processor.py
|
|
9
9
|
lalamo/quantization.py
|
|
10
|
+
lalamo/registry_abc.py
|
|
10
11
|
lalamo/sampling.py
|
|
11
12
|
lalamo/utils.py
|
|
12
13
|
lalamo.egg-info/PKG-INFO
|
|
@@ -61,4 +62,6 @@ lalamo/modules/torch_interop.py
|
|
|
61
62
|
lalamo/modules/utils.py
|
|
62
63
|
tests/test_generation.py
|
|
63
64
|
tests/test_huggingface_models.py
|
|
64
|
-
tests/
|
|
65
|
+
tests/test_model_spec.py
|
|
66
|
+
tests/test_parameter_tree.py
|
|
67
|
+
tests/test_registry_abc.py
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from lalamo.model_import.decoder_configs.huggingface.llama import HFLlamaConfig
|
|
2
|
+
from lalamo.model_import.model_specs.common import ModelSpec, WeightsType
|
|
3
|
+
|
|
4
|
+
EXAMPLE_JSON = {
|
|
5
|
+
"vendor": "Meta",
|
|
6
|
+
"family": "Llama-3.2",
|
|
7
|
+
"name": "Llama-3.2-1B-Instruct",
|
|
8
|
+
"size": "1B",
|
|
9
|
+
"quantization": None,
|
|
10
|
+
"repo": "meta-llama/Llama-3.2-1B-Instruct",
|
|
11
|
+
"config_type": "HFLlamaConfig",
|
|
12
|
+
"output_parser_regex": None,
|
|
13
|
+
"system_role_name": "system",
|
|
14
|
+
"user_role_name": "user",
|
|
15
|
+
"assistant_role_name": "assistant",
|
|
16
|
+
"tool_role_name": "tool",
|
|
17
|
+
"weights_type": "safetensors",
|
|
18
|
+
"use_cases": [],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_deserialization() -> None:
|
|
23
|
+
model_spec = ModelSpec.from_json(EXAMPLE_JSON)
|
|
24
|
+
assert model_spec.vendor == "Meta"
|
|
25
|
+
assert model_spec.family == "Llama-3.2"
|
|
26
|
+
assert model_spec.name == "Llama-3.2-1B-Instruct"
|
|
27
|
+
assert model_spec.size == "1B"
|
|
28
|
+
assert model_spec.quantization is None
|
|
29
|
+
assert model_spec.repo == "meta-llama/Llama-3.2-1B-Instruct"
|
|
30
|
+
assert model_spec.config_type is HFLlamaConfig
|
|
31
|
+
assert model_spec.output_parser_regex is None
|
|
32
|
+
assert model_spec.system_role_name == "system"
|
|
33
|
+
assert model_spec.user_role_name == "user"
|
|
34
|
+
assert model_spec.assistant_role_name == "assistant"
|
|
35
|
+
assert model_spec.tool_role_name == "tool"
|
|
36
|
+
assert model_spec.weights_type == WeightsType.SAFETENSORS
|
|
37
|
+
assert model_spec.use_cases == ()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_consistency() -> None:
|
|
41
|
+
spec = ModelSpec(
|
|
42
|
+
vendor="Meta",
|
|
43
|
+
family="Llama-3.2",
|
|
44
|
+
name="Llama-3.2-1B-Instruct",
|
|
45
|
+
size="1B",
|
|
46
|
+
quantization=None,
|
|
47
|
+
repo="meta-llama/Llama-3.2-1B-Instruct",
|
|
48
|
+
config_type=HFLlamaConfig,
|
|
49
|
+
use_cases=tuple(),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
assert ModelSpec.from_json(spec.to_json()) == spec
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# test_registry_abc.py
|
|
2
|
+
import gc
|
|
3
|
+
import weakref
|
|
4
|
+
|
|
5
|
+
from lalamo.registry_abc import RegistryABC
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_basic_registration_and_multilevel() -> None:
|
|
9
|
+
class AbstractFoo(RegistryABC):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
class A(AbstractFoo):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
class B(A):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
# __get_descendants__ is a class method returning a list
|
|
19
|
+
assert isinstance(AbstractFoo.__descendants__(), list)
|
|
20
|
+
|
|
21
|
+
# Order is not guaranteed; compare as sets
|
|
22
|
+
assert set(AbstractFoo.__descendants__()) >= {A, B}
|
|
23
|
+
assert set(A.__descendants__()) == {B}
|
|
24
|
+
assert set(B.__descendants__()) == set()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_excludes_classes_that_directly_list_registryabc() -> None:
|
|
28
|
+
class AbstractFoo(RegistryABC):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
class A(AbstractFoo):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
# This class directly lists RegistryABC, so it must be excluded everywhere.
|
|
35
|
+
class Hybrid(AbstractFoo, RegistryABC):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
assert A in AbstractFoo.__descendants__() # tracked
|
|
39
|
+
assert Hybrid not in AbstractFoo.__descendants__() # excluded
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_no_cross_pollination_between_roots() -> None:
|
|
43
|
+
class AbstractFoo(RegistryABC):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
class AbstractBar(RegistryABC):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
class AFoo(AbstractFoo):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
class BBar(AbstractBar):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
assert AFoo in AbstractFoo.__descendants__() # in its own tree
|
|
56
|
+
assert BBar not in AbstractFoo.__descendants__() # not leaking across
|
|
57
|
+
|
|
58
|
+
assert BBar in AbstractBar.__descendants__() # in its own tree
|
|
59
|
+
assert AFoo not in AbstractBar.__descendants__() # not leaking across
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_mixin_and_indirect_registryabc_paths_are_included() -> None:
|
|
63
|
+
class AbstractFoo(RegistryABC):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
class OtherRoot(RegistryABC):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
class Mixin:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
# Indirect path via a base that itself descends from RegistryABC should *not*
|
|
73
|
+
# exclude the class, because the class does not directly list RegistryABC.
|
|
74
|
+
class Indirect(OtherRoot):
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
class D(Mixin, AbstractFoo, Indirect):
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
assert D in AbstractFoo.__descendants__()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_dynamic_updates_on_late_definitions() -> None:
|
|
84
|
+
class AbstractFoo(RegistryABC):
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
assert set(AbstractFoo.__descendants__()) == set()
|
|
88
|
+
|
|
89
|
+
class A(AbstractFoo):
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
assert A in AbstractFoo.__descendants__()
|
|
93
|
+
|
|
94
|
+
class B(A):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
# Newly defined class appears without needing to "refresh" anything
|
|
98
|
+
assert B in AbstractFoo.__descendants__()
|
|
99
|
+
# And it should also appear under A
|
|
100
|
+
assert B in A.__descendants__()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_weakset_allows_garbage_collection() -> None:
|
|
104
|
+
class AbstractFoo(RegistryABC):
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
def make_temp() -> type:
|
|
108
|
+
class Temp(AbstractFoo):
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
return Temp
|
|
112
|
+
|
|
113
|
+
temp_cls = make_temp()
|
|
114
|
+
wr = weakref.ref(temp_cls)
|
|
115
|
+
|
|
116
|
+
# It should be registered while alive
|
|
117
|
+
assert temp_cls in AbstractFoo.__descendants__()
|
|
118
|
+
assert wr() is not None
|
|
119
|
+
|
|
120
|
+
# Drop the strong ref and collect
|
|
121
|
+
del temp_cls
|
|
122
|
+
gc.collect()
|
|
123
|
+
|
|
124
|
+
# Weak ref is cleared, and the registry reflects that
|
|
125
|
+
assert wr() is None
|
|
126
|
+
names = {cls.__name__ for cls in AbstractFoo.__descendants__()}
|
|
127
|
+
assert "Temp" not in names
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_descendants_method_exists_on_all_registryabc_subclasses() -> None:
|
|
131
|
+
class Root(RegistryABC):
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
class Mid(Root):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
class Leaf(Mid):
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
# Method exists and returns lists on all levels
|
|
141
|
+
for C in (Root, Mid, Leaf): # noqa: N806
|
|
142
|
+
assert hasattr(C, "__get_descendants__")
|
|
143
|
+
assert callable(C.__descendants__)
|
|
144
|
+
assert isinstance(C.__descendants__(), list)
|
|
145
|
+
|
|
146
|
+
assert Leaf in Mid.__descendants__()
|
|
147
|
+
assert Leaf in Root.__descendants__()
|
|
148
|
+
assert Mid in Root.__descendants__()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|