luna-usecase-template 0.0.2a2__py3-none-any.whl

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.
@@ -0,0 +1,15 @@
1
+ from .collection import UcInstanceCollection
2
+ from .data import UcData
3
+ from .formulation import UcFormulation
4
+ from .instance import UcInstance
5
+ from .registry import Registry
6
+ from .solution import UcSolution
7
+
8
+ __all__ = [
9
+ "Registry",
10
+ "UcData",
11
+ "UcFormulation",
12
+ "UcInstance",
13
+ "UcInstanceCollection",
14
+ "UcSolution",
15
+ ]
@@ -0,0 +1,50 @@
1
+ from pathlib import Path
2
+ from typing import Self
3
+
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+
7
+ class UcBaseModel(BaseModel):
8
+ """Use case base model with config."""
9
+
10
+ model_config = ConfigDict(extra="forbid", strict=True)
11
+
12
+ def store(self, path: Path | str) -> None:
13
+ """Store model to file.
14
+
15
+ Supported file types are `json` and `pkl`
16
+ """
17
+ if isinstance(path, str):
18
+ path = Path(path)
19
+
20
+ match path.suffix:
21
+ case ".json":
22
+ with open(path, "w") as f:
23
+ f.write(self.model_dump_json())
24
+ case _:
25
+ msg = (
26
+ f"Unsupported suffix '{path.suffix}'. "
27
+ f"Only .json is supported as of now."
28
+ )
29
+ raise ValueError(msg)
30
+
31
+ @classmethod
32
+ def load(cls, path: Path | str) -> Self:
33
+ """Load model from file.
34
+
35
+ Supported file types are `json` and `pkl`
36
+ """
37
+ if isinstance(path, str):
38
+ path = Path(path)
39
+
40
+ match path.suffix:
41
+ case ".json":
42
+ with open(path) as f:
43
+ return cls.model_validate_json(f.read())
44
+
45
+ case _:
46
+ msg = (
47
+ f"Unsupported suffix '{path.suffix}'. "
48
+ f"Only .json is supported as of now."
49
+ )
50
+ raise ValueError(msg)
@@ -0,0 +1,29 @@
1
+ from typing import Self
2
+
3
+ from pydantic import ModelWrapValidatorHandler, model_validator
4
+
5
+ from .base import UcBaseModel
6
+ from .instance import UcInstance
7
+
8
+
9
+ class UcInstanceCollection[I: UcInstance](UcBaseModel):
10
+ """Instance collection base class."""
11
+
12
+ instances: list[I]
13
+
14
+ @model_validator(mode="wrap")
15
+ @classmethod
16
+ def validate_instance(
17
+ cls, data: ..., handler: ModelWrapValidatorHandler[Self]
18
+ ) -> Self:
19
+ """Validate pydantic model."""
20
+ if cls.__name__ != "UcInstanceCollection":
21
+ return handler(data)
22
+
23
+ from .registry import Registry # noqa: PLC0415
24
+
25
+ return Registry.collection_adapter(base_class=cls).validate_python(data)
26
+
27
+ @classmethod
28
+ def generate_predefined(cls) -> Self:
29
+ """Generate a predefined instance collection of the use case."""
@@ -0,0 +1,31 @@
1
+ from abc import abstractmethod
2
+ from inspect import isabstract
3
+ from typing import Self
4
+
5
+ from pydantic import ModelWrapValidatorHandler, model_validator
6
+
7
+ from .base import UcBaseModel
8
+ from .named import Named
9
+
10
+
11
+ class UcData(UcBaseModel, Named):
12
+ """Use Case data base class."""
13
+
14
+ data_name: str
15
+
16
+ @abstractmethod
17
+ def to_string(self) -> str:
18
+ """Return the model data definition."""
19
+ ...
20
+
21
+ @model_validator(mode="wrap")
22
+ @classmethod
23
+ def validate_instance(
24
+ cls, data: ..., handler: ModelWrapValidatorHandler[Self]
25
+ ) -> Self:
26
+ """Validate pydantic model."""
27
+ if isabstract(cls):
28
+ from .registry import Registry # noqa: PLC0415
29
+
30
+ return Registry.data_adapter(base_class=cls).validate_python(data)
31
+ return handler(data)
@@ -0,0 +1,45 @@
1
+ from abc import abstractmethod
2
+ from inspect import isabstract
3
+ from typing import Self
4
+
5
+ from luna_model import Model, Solution
6
+ from pydantic import ModelWrapValidatorHandler, model_validator
7
+
8
+ from .base import UcBaseModel
9
+ from .data import UcData
10
+ from .named import Named
11
+ from .solution import UcSolution
12
+
13
+
14
+ class UcFormulation[D: UcData, S: UcSolution](UcBaseModel, Named):
15
+ """Use case formulation base class."""
16
+
17
+ @staticmethod
18
+ @abstractmethod
19
+ def to_string(data: D) -> str:
20
+ """Print the formulation."""
21
+ ...
22
+
23
+ @staticmethod
24
+ @abstractmethod
25
+ def formulate(data: D) -> Model:
26
+ """Formulate the use case as a concrete AqModel."""
27
+ ...
28
+
29
+ @staticmethod
30
+ @abstractmethod
31
+ def interpret(solution: Solution, data: D) -> S:
32
+ """Interpret the solution to the AqModel."""
33
+ ...
34
+
35
+ @model_validator(mode="wrap")
36
+ @classmethod
37
+ def validate_instance(
38
+ cls, data: ..., handler: ModelWrapValidatorHandler[Self]
39
+ ) -> Self:
40
+ """Validate the pydantic model."""
41
+ if isabstract(cls):
42
+ from .registry import Registry # noqa: PLC0415
43
+
44
+ return Registry.formulation_adapter(base_class=cls).validate_python(data)
45
+ return handler(data)
@@ -0,0 +1,52 @@
1
+ from typing import Self
2
+
3
+ from luna_model import Model, Solution
4
+ from pydantic import ModelWrapValidatorHandler, model_validator
5
+
6
+ from .base import UcBaseModel
7
+ from .data import UcData
8
+ from .formulation import UcFormulation
9
+ from .solution import UcSolution
10
+
11
+
12
+ class UcInstance[D: UcData, F: UcFormulation, S: UcSolution](UcBaseModel):
13
+ """Use case instance is the collection of the data and formulation."""
14
+
15
+ data: D
16
+ formulation: F
17
+
18
+ def to_string(self) -> str:
19
+ """Get a string representation of the instance."""
20
+ data_descr = "Data:"
21
+ data_descr += self.data.to_string()
22
+ form_descr = "Formulation:"
23
+ form_descr += self.formulation.to_string(self.data)
24
+ return f"{data_descr}\n{form_descr}"
25
+
26
+ @property
27
+ def name(self) -> str:
28
+ """Get name of the instance."""
29
+ return self.formulation.name + "<" + self.data.data_name + ">"
30
+
31
+ def formulate(self) -> Model:
32
+ """Formulate the instance as AqModel."""
33
+ model = self.formulation.formulate(self.data)
34
+ model.name = self.name
35
+ return model
36
+
37
+ def interpret(self, solution: Solution) -> S:
38
+ """Interpret the solution to the AqModel."""
39
+ return self.formulation.interpret(solution=solution, data=self.data)
40
+
41
+ @model_validator(mode="wrap")
42
+ @classmethod
43
+ def validate_instance(
44
+ cls, data: ..., handler: ModelWrapValidatorHandler[Self]
45
+ ) -> Self:
46
+ """Validate the pydantic model."""
47
+ if cls.__name__ != "UcInstance":
48
+ return handler(data)
49
+
50
+ from .registry import Registry # noqa: PLC0415
51
+
52
+ return Registry.instance_adapter(base_class=cls).validate_python(data)
@@ -0,0 +1,19 @@
1
+ from typing import Literal, get_args, get_origin
2
+
3
+
4
+ class Named[N: str]:
5
+ """Named pydantic class mixin."""
6
+
7
+ name: N
8
+
9
+ @classmethod
10
+ def get_name(cls) -> str:
11
+ """Return the name of the class."""
12
+ name_type = cls.__dict__["__annotations__"]["name"]
13
+ if not (get_origin(name_type) is Literal and len(get_args(name_type)) == 1):
14
+ msg = (
15
+ f"Invalid `name` field in '{cls.__name__}'. "
16
+ "Required to be a Literal of a single string."
17
+ )
18
+ raise RuntimeError(msg)
19
+ return get_args(name_type)[0]
@@ -0,0 +1,219 @@
1
+ import functools
2
+ import importlib
3
+ import inspect
4
+ import pkgutil
5
+ from collections.abc import Callable, Iterable
6
+ from types import ModuleType
7
+ from typing import ClassVar, Union
8
+
9
+ from pydantic import TypeAdapter
10
+
11
+ from .collection import UcInstanceCollection
12
+ from .data import UcData
13
+ from .formulation import UcFormulation
14
+ from .instance import UcInstance
15
+ from .solution import UcSolution
16
+
17
+
18
+ def _check_concrete[T: type](f: Callable[[T], T]) -> Callable[[T], T]:
19
+ @functools.wraps(f)
20
+ def _inner(t: T) -> T:
21
+ if inspect.isabstract(t):
22
+ msg = (
23
+ f"Abstract class '{t.__name__}' detected. "
24
+ "Registry can only hold concrete models."
25
+ )
26
+
27
+ raise ValueError(msg)
28
+ return f(t)
29
+
30
+ return _inner
31
+
32
+
33
+ def _generic_adapter(
34
+ types: Iterable[type], base_class: type | None = None
35
+ ) -> TypeAdapter:
36
+ if base_class is not None:
37
+ types = [t for t in types if issubclass(t, base_class)]
38
+ else:
39
+ types = list(types)
40
+ if len(types) == 0:
41
+ msg = "No suitable types registered."
42
+ raise ValueError(msg)
43
+ return TypeAdapter(Union[*types])
44
+
45
+
46
+ class Registry:
47
+ """Registry of all use case classes."""
48
+
49
+ _instances: ClassVar[list[type[UcInstance]]] = []
50
+ _datas: ClassVar[list[type[UcData]]] = []
51
+ _formulations: ClassVar[list[type[UcFormulation]]] = []
52
+ _solutions: ClassVar[list[type[UcSolution]]] = []
53
+ _collections: ClassVar[list[type[UcInstanceCollection]]] = []
54
+ _registered: ClassVar[set[type]] = set()
55
+
56
+ @staticmethod
57
+ def adapter() -> TypeAdapter:
58
+ """Get the type adapter of all registered classes."""
59
+ types = (
60
+ Registry._instances
61
+ + Registry._datas
62
+ + Registry._formulations
63
+ + Registry._solutions
64
+ + Registry._collections
65
+ )
66
+ return TypeAdapter(Union[*types])
67
+
68
+ @staticmethod
69
+ def _check_registered[T: type](f: Callable[[T], T]) -> Callable[[T], T]:
70
+ @functools.wraps(f)
71
+ def _inner(t: T) -> T:
72
+ if t not in Registry._registered:
73
+ Registry._registered.add(t)
74
+ return f(t)
75
+ return t
76
+
77
+ return _inner
78
+
79
+ @staticmethod
80
+ def instance_adapter(base_class: None | type[UcInstance] = None) -> TypeAdapter:
81
+ """Get the type adapter of all registered instance classes."""
82
+ return _generic_adapter(Registry._instances, base_class)
83
+
84
+ @staticmethod
85
+ def data_adapter(base_class: None | type[UcData] = None) -> TypeAdapter:
86
+ """Get the type adapter of all registered data classes."""
87
+ return _generic_adapter(Registry._datas, base_class)
88
+
89
+ @staticmethod
90
+ def formulation_adapter(
91
+ base_class: None | type[UcFormulation] = None,
92
+ ) -> TypeAdapter:
93
+ """Get the type adapter of all registered formulation classes."""
94
+ return _generic_adapter(Registry._formulations, base_class)
95
+
96
+ @staticmethod
97
+ def solution_adapter(base_class: None | type[UcSolution] = None) -> TypeAdapter:
98
+ """Get the type adapter of all registered solution classes."""
99
+ return _generic_adapter(Registry._solutions, base_class)
100
+
101
+ @staticmethod
102
+ def collection_adapter(
103
+ base_class: None | type[UcInstanceCollection] = None,
104
+ ) -> TypeAdapter:
105
+ """Get the type adapter of all registered collection classes."""
106
+ return _generic_adapter(Registry._collections, base_class)
107
+
108
+ @staticmethod
109
+ @_check_registered
110
+ @_check_concrete
111
+ def add_instance[T: type[UcInstance]](instance: T) -> T:
112
+ """Register an instance class."""
113
+ Registry._instances.append(instance)
114
+ return instance
115
+
116
+ @staticmethod
117
+ @_check_registered
118
+ @_check_concrete
119
+ def add_data[T: type[UcData]](data: T) -> T:
120
+ """Register a data class."""
121
+ Registry._datas.append(data)
122
+ return data
123
+
124
+ @staticmethod
125
+ @_check_registered
126
+ @_check_concrete
127
+ def add_formulation[T: type[UcFormulation]](formulation: T) -> T:
128
+ """Register a formulation class."""
129
+ Registry._formulations.append(formulation)
130
+ return formulation
131
+
132
+ @staticmethod
133
+ @_check_registered
134
+ @_check_concrete
135
+ def add_solution[T: type[UcSolution]](solution: T) -> T:
136
+ """Register a solution class."""
137
+ Registry._solutions.append(solution)
138
+ return solution
139
+
140
+ @staticmethod
141
+ @_check_registered
142
+ @_check_concrete
143
+ def add_collection[T: type[UcInstanceCollection]](collection: T) -> T:
144
+ """Register a collection class."""
145
+ Registry._collections.append(collection)
146
+ return collection
147
+
148
+ @staticmethod
149
+ def add[T: type](input_class: T) -> T:
150
+ """Register any class."""
151
+ if issubclass(input_class, UcInstance):
152
+ Registry.add_instance(input_class)
153
+ elif issubclass(input_class, UcData):
154
+ Registry.add_data(input_class)
155
+ elif issubclass(input_class, UcFormulation):
156
+ Registry.add_formulation(input_class)
157
+ elif issubclass(input_class, UcSolution):
158
+ Registry.add_solution(input_class)
159
+ elif issubclass(input_class, UcInstanceCollection):
160
+ Registry.add_collection(input_class)
161
+ else:
162
+ msg = f"Attempted to register unknown type {input_class}."
163
+ raise TypeError(msg)
164
+ return input_class
165
+
166
+ @staticmethod
167
+ def can_register(input_class: type) -> bool:
168
+ """Check if a class can be registered."""
169
+ if input_class in Registry._registered or inspect.isabstract(input_class):
170
+ return False
171
+ if input_class.__name__ in ("UcInstance", "UcInstanceCollection"):
172
+ return False
173
+
174
+ return bool(
175
+ issubclass(input_class, UcInstance)
176
+ or issubclass(input_class, UcData)
177
+ or issubclass(input_class, UcFormulation)
178
+ or issubclass(input_class, UcSolution)
179
+ or issubclass(input_class, UcInstanceCollection)
180
+ )
181
+
182
+ @staticmethod
183
+ def add_module(mod: ModuleType) -> None:
184
+ """Add a whole module."""
185
+ for info in pkgutil.walk_packages(mod.__path__, prefix=f"{mod.__name__}."):
186
+ submod = importlib.import_module(info.name) # nosemgrep: non-literal-import
187
+
188
+ for _, obj in inspect.getmembers(submod, inspect.isclass):
189
+ if Registry.can_register(obj):
190
+ Registry.add(obj)
191
+
192
+ @staticmethod
193
+ def validate(s: ...) -> ...:
194
+ """Validate a python object."""
195
+ return Registry.adapter().validate_python(s)
196
+
197
+ @staticmethod
198
+ def validate_json(s: str | bytes | bytearray) -> ...:
199
+ """Validate a json string."""
200
+ return Registry.adapter().validate_json(s)
201
+
202
+ @staticmethod
203
+ def to_string() -> str:
204
+ """Return a string representation of the registry and contents."""
205
+ lines = [
206
+ "UcData: " + ", ".join(f"{t.__name__}" for t in Registry._datas),
207
+ "UcSolution: " + ", ".join(f"{t.__name__}" for t in Registry._solutions),
208
+ "UcInstance: " + ", ".join(f"{t.__name__}" for t in Registry._instances),
209
+ "UcFormulation: "
210
+ + ", ".join(f"{t.__name__}" for t in Registry._formulations),
211
+ "UcInstanceCollection: "
212
+ + ", ".join(f"{t.__name__}" for t in Registry._collections),
213
+ ]
214
+ return "\n".join(lines)
215
+
216
+ @staticmethod
217
+ def print() -> None:
218
+ """Print the registry and contents."""
219
+ print(Registry.to_string()) # noqa: T201
@@ -0,0 +1,38 @@
1
+ from abc import abstractmethod
2
+ from inspect import isabstract
3
+ from typing import Protocol, Self, runtime_checkable
4
+
5
+ from pydantic import ModelWrapValidatorHandler, model_validator
6
+
7
+ from .base import UcBaseModel
8
+ from .data import UcData
9
+ from .named import Named
10
+
11
+
12
+ class UcSolution(UcBaseModel, Named):
13
+ """Use case solution base class."""
14
+
15
+ @abstractmethod
16
+ def to_string(self) -> str:
17
+ """Print the solution."""
18
+
19
+ @model_validator(mode="wrap")
20
+ @classmethod
21
+ def validate_solution(
22
+ cls, data: ..., handler: ModelWrapValidatorHandler[Self]
23
+ ) -> Self:
24
+ """Validate the pydantic model."""
25
+ if isabstract(cls):
26
+ from .registry import Registry # noqa: PLC0415
27
+
28
+ return Registry.solution_adapter(base_class=cls).validate_python(data)
29
+ return handler(data)
30
+
31
+
32
+ @runtime_checkable
33
+ class Visulaizable[T: UcData](Protocol):
34
+ """Visualizable protocol."""
35
+
36
+ def visualize(self, data: T | None = None) -> None:
37
+ """Visualize the solution."""
38
+ ...
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: luna-usecase-template
3
+ Version: 0.0.2a2
4
+ Summary: Template for optimization use cases.
5
+ Keywords: aqarios,luna,quantum computing,quantum optimization,optimization,sdk
6
+ Author: Aqarios GmbH
7
+ Author-email: Aqarios GmbH <info@aqarios.com>
8
+ License-Expression: Apache-2.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Requires-Dist: luna-model>=0.5.3
13
+ Requires-Dist: pydantic>=2.12.5
14
+ Maintainer: Maximilian Janetschek, David Bucher, Jonas Blenninger
15
+ Maintainer-email: Maximilian Janetschek <maximilian.janetschek@aqarios.com>, David Bucher <david.bucher@aqarios.com>, Jonas Blenninger <jonas.blenninger@aqarios.com>
16
+ Requires-Python: >=3.11.0
17
+ Description-Content-Type: text/markdown
18
+
19
+ # Use Case Library
20
+
21
+ A Python SDK for defining optimization use cases in the [Luna](https://aqarios.com) quantum optimization framework.
22
+
23
+ ## Overview
24
+
25
+ `usecase-lib` provides a structured template for defining optimization problems with:
26
+
27
+ - **Data** — Problem input data (`UcData`)
28
+ - **Formulation** — How the problem maps to a mathematical model (`UcFormulation`)
29
+ - **Solution** — Interpretation of solver output (`UcSolution`)
30
+ - **Instance** — A concrete pairing of data + formulation (`UcInstance`)
31
+ - **Collection** — Groups of instances (`UcInstanceCollection`)
32
+
33
+ All types support JSON serialization and polymorphic deserialization via a central `Registry`.
34
+
35
+ ## Requirements
36
+
37
+ - Python >= 3.13.0
38
+
39
+ ## Quick Start
40
+
41
+ ### Define a use case
42
+
43
+ ```python
44
+ from typing import Literal, override
45
+
46
+ from luna_model import Model, Solution
47
+ from usecase_lib import Registry, UcData, UcFormulation, UcInstance, UcSolution
48
+
49
+
50
+ class MyData(UcData):
51
+ name: Literal["my_data"] = "my_data"
52
+ values: list[float]
53
+
54
+ @override
55
+ def to_string(self) -> str:
56
+ return str(self.values)
57
+
58
+
59
+ class MySolution(UcSolution):
60
+ name: Literal["my_solution"] = "my_solution"
61
+ result: float
62
+
63
+ @override
64
+ def to_string(self) -> str:
65
+ return str(self.result)
66
+
67
+
68
+ class MyFormulation(UcFormulation[MyData, MySolution]):
69
+ name: Literal["my_formulation"] = "my_formulation"
70
+
71
+ @override
72
+ @staticmethod
73
+ def to_string(data: MyData) -> str:
74
+ return "minimize sum(values)"
75
+
76
+ @override
77
+ @staticmethod
78
+ def formulate(data: MyData) -> Model:
79
+ m = Model("MyProblem")
80
+ # ... build your model ...
81
+ return m
82
+
83
+ @override
84
+ @staticmethod
85
+ def interpret(solution: Solution, data: MyData) -> MySolution:
86
+ return MySolution(result=0.0)
87
+
88
+
89
+ class MyInstance(UcInstance):
90
+ data: MyData
91
+ formulation: MyFormulation
92
+ ```
93
+
94
+ ### Register and serialize
95
+
96
+ ```python
97
+ # Register classes (also works as @Registry.add decorator)
98
+ Registry.add(MyData)
99
+ Registry.add(MySolution)
100
+ Registry.add(MyFormulation)
101
+ Registry.add(MyInstance)
102
+
103
+ # Create and serialize
104
+ data = MyData(data_name="example", values=[1.0, 2.0, 3.0])
105
+ instance = MyInstance(data=data, formulation=MyFormulation())
106
+ instance.store("instance.json")
107
+
108
+ # Deserialize with polymorphic type dispatch
109
+ loaded = MyInstance.load("instance.json")
110
+ ```
111
+
112
+ ## Architecture
113
+
114
+ Every concrete `UcData`, `UcFormulation`, and `UcSolution` subclass must define a `name` field as a `Literal["unique_string"]`. This enables the `Registry` to distinguish types during deserialization.
115
+
116
+ Abstract base classes use Pydantic wrap validators to automatically dispatch validation to the correct registered subclass, so you can deserialize from a base type without knowing the concrete type ahead of time.
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ uv sync # Install dependencies
122
+ ruff check . --fix # Lint with auto-fix
123
+ ruff format . # Format code
124
+ uv build # Build package
125
+ ```
126
+
127
+ ## License
128
+
129
+ Apache-2.0
@@ -0,0 +1,12 @@
1
+ luna_usecase_template/__init__.py,sha256=FlETY5PXi0M255ShJn2oz76bns9rKu9G7a2beEulpsI,336
2
+ luna_usecase_template/base.py,sha256=1IC08mmpBcAq-ANl1ggfoSoTQqSmSIZTaBqLFHscRu0,1397
3
+ luna_usecase_template/collection.py,sha256=paC6cs6tg545_T3InPG1y73lxQl96azW55h4BLMcugw,831
4
+ luna_usecase_template/data.py,sha256=tu1vUXO0HN9s695Hc29ZukND0wYGmIZDgnFQzA4WwQo,807
5
+ luna_usecase_template/formulation.py,sha256=BZzPLcoOKgplkWD3fw8MD03_dXx5QH4KYZxL-oIIpA0,1250
6
+ luna_usecase_template/instance.py,sha256=R3NYiFsirdq5wKK02W-tnQQWV9v3q-_JwImrQRYDypw,1683
7
+ luna_usecase_template/named.py,sha256=qtQJD5tdNmILKCmc7sNneZKu-aIGRlndM5AWt5LAEkA,593
8
+ luna_usecase_template/registry.py,sha256=cIHKhZaTHhGErmmw7ngvLZmBl2xihwY6tK21zUnmZy4,7506
9
+ luna_usecase_template/solution.py,sha256=XI9pGB46sLaecMBFdpJTx6b4zOV0imls6nFYXBDRJ94,1033
10
+ luna_usecase_template-0.0.2a2.dist-info/WHEEL,sha256=mydTeHxOpFHo-DnYhAd_3ATePms-g4rrYvM7wJK8P-U,80
11
+ luna_usecase_template-0.0.2a2.dist-info/METADATA,sha256=9xfwux_zaArnxlet6EKugpPUhZ4T93JIwadHB72UKS0,3775
12
+ luna_usecase_template-0.0.2a2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.10.9
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any