sqlcrucible 0.4.0__tar.gz → 0.5.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.
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/PKG-INFO +1 -1
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/guide/advanced.md +15 -7
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/pyproject.toml +7 -2
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/_version.py +2 -2
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/core.py +16 -9
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/descriptors.py +9 -23
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/stubs/__init__.py +51 -23
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/stubs/codegen.py +5 -2
- sqlcrucible-0.5.0/tests/entity/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_generic_sti.py +32 -1
- sqlcrucible-0.5.0/tests/stubs/test_stub_generation.py +52 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/uv.lock +29 -24
- sqlcrucible-0.4.0/tests/stubs/test_stub_generation.py +0 -44
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/.github/workflows/ci.yml +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/.github/workflows/docs.yml +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/.gitignore +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/.python-version +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/LICENSE +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/README.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/comparison.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/getting-started.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/guide/defining-entities.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/guide/field-mapping.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/guide/inheritance.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/guide/orm-descriptors.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/guide/relationships.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/guide/type-conversion.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/index.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/docs/reference/api.md +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/mkdocs.yml +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/noxfile.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/_types/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/_types/annotations.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/_types/forward_refs.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/_types/match.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/_types/params.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/_types/transformer.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/caching.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/context.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/dicts.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/exceptions.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/function.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/literals.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/noop.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/registry.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/sequences.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/unions.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/conversion/unwrap.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/annotations.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/automodel.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/column_projection.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/field_definitions.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/field_resolution.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/sa_conversion.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/entity/sa_type.py +0 -0
- /sqlcrucible-0.4.0/tests/__init__.py → /sqlcrucible-0.5.0/src/sqlcrucible/py.typed +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/stubs/__main__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/stubs/discovery.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/src/sqlcrucible/stubs/serialization.py +0 -0
- {sqlcrucible-0.4.0/tests/_types → sqlcrucible-0.5.0/tests}/__init__.py +0 -0
- {sqlcrucible-0.4.0/tests/conversion → sqlcrucible-0.5.0/tests/_types}/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/_types/test_annotations.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/_types/test_annotations_properties.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/_types/test_forward_refs.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/_types/test_params.py +0 -0
- {sqlcrucible-0.4.0/tests/entity → sqlcrucible-0.5.0/tests/conversion}/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/conftest.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_caching.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_caching_properties.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_dicts.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_literals.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_literals_properties.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_noop.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_registry.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_sequences.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_unions.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/conversion/test_unwrap.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/composite/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/composite/conftest.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/composite/test_from_sa_model.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/composite/test_projections.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/composite/test_to_column_dict.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/composite/test_to_sa_model.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/generic_sti_models.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/orm_descriptors/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/orm_descriptors/conftest.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/orm_descriptors/test_association_proxy.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/orm_descriptors/test_hybrid_property.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/orm_descriptors/test_writable_descriptors.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_attrs_entity.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_concrete_table_inheritance.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_conversion_context.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_custom_sa_model.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_dataclass_entity.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_explicit_table.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_generic_entity.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_joined_table_inheritance.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_pydantic_entity.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_readonly_field_serialisation.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_annotated_metadata.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_back_populates.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_cycles.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_eager.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_many_to_many.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_one_to_many_child.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_one_to_many_parent.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_one_to_one.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_self_referential.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_sa_type.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_single_table_inheritance.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/strategies.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/__init__.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/conftest.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/sample_models.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_build_import_block.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_codegen.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_construct_model_def.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_discovery.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_generate_model_defs.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_sa_field_type.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_serialization.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_typecheck_columns.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_typecheck_entity_preservation.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_typecheck_excluded_fields.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_typecheck_relationships.py +0 -0
- {sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/stubs/test_typecheck_sa_type.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlcrucible
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Define a single model that works as both Pydantic and SQLAlchemy, with explicit conversion between the two
|
|
5
5
|
Project-URL: Homepage, https://sqlcrucible.rdrj.uk
|
|
6
6
|
Project-URL: Issues, https://github.com/RichardDRJ/sqlcrucible/issues
|
|
@@ -105,8 +105,18 @@ python -m sqlcrucible.stubs myapp.models --output typings/
|
|
|
105
105
|
!!! tip
|
|
106
106
|
For projects with entities spread across many modules, create a single module that imports them all, then generate stubs from that.
|
|
107
107
|
|
|
108
|
+
The output directory contains a PEP 561 `sqlcrucible-stubs` partial stub package. Point your type checker at that directory (not at the package inside it); the checker discovers `sqlcrucible-stubs` and merges it with the installed `sqlcrucible`, overriding `SAType` and adding the generated model types while everything else falls through to the real package.
|
|
109
|
+
|
|
108
110
|
### Configuring Type Checkers
|
|
109
111
|
|
|
112
|
+
=== "ty"
|
|
113
|
+
|
|
114
|
+
```toml
|
|
115
|
+
# pyproject.toml
|
|
116
|
+
[tool.ty.environment]
|
|
117
|
+
extra-paths = ["stubs"]
|
|
118
|
+
```
|
|
119
|
+
|
|
110
120
|
=== "Pyright"
|
|
111
121
|
|
|
112
122
|
```toml
|
|
@@ -123,13 +133,11 @@ python -m sqlcrucible.stubs myapp.models --output typings/
|
|
|
123
133
|
mypy_path = "stubs"
|
|
124
134
|
```
|
|
125
135
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
extra-paths = ["stubs"]
|
|
132
|
-
```
|
|
136
|
+
!!! note
|
|
137
|
+
Mypy picks up the typed `sqlcrucible` package but does not currently
|
|
138
|
+
resolve the generated `SAType[Entity]` model types from the partial
|
|
139
|
+
stub package, so `SAType[...]` access falls back to `Any` under mypy.
|
|
140
|
+
Use ty or pyright for full `SAType` column typing.
|
|
133
141
|
|
|
134
142
|
### Keeping Stubs Updated
|
|
135
143
|
|
|
@@ -44,11 +44,11 @@ test = [
|
|
|
44
44
|
"pytest-cov>=6.0.0",
|
|
45
45
|
"hypothesis>=6.100.0",
|
|
46
46
|
"pyright",
|
|
47
|
-
"ty",
|
|
47
|
+
"ty>=0.0.41",
|
|
48
48
|
]
|
|
49
49
|
typecheck = [
|
|
50
50
|
"pyright",
|
|
51
|
-
"ty",
|
|
51
|
+
"ty>=0.0.41",
|
|
52
52
|
]
|
|
53
53
|
lint = [
|
|
54
54
|
"ruff",
|
|
@@ -69,6 +69,11 @@ dev = [
|
|
|
69
69
|
{ include-group = "depcheck" },
|
|
70
70
|
]
|
|
71
71
|
|
|
72
|
+
[tool.pyright]
|
|
73
|
+
# Required for TypeForm support (PEP 747): without this, pyright rejects string
|
|
74
|
+
# forward references and union special forms passed to readonly_field().
|
|
75
|
+
enableExperimentalFeatures = true
|
|
76
|
+
|
|
72
77
|
[tool.pytest.ini_options]
|
|
73
78
|
addopts = "--doctest-modules"
|
|
74
79
|
testpaths = ["src", "tests"]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.5.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 5, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -278,7 +278,11 @@ class SQLCrucibleEntity:
|
|
|
278
278
|
|
|
279
279
|
This method converts a SQLAlchemy model instance into the corresponding
|
|
280
280
|
entity class. For polymorphic models, it automatically selects the most
|
|
281
|
-
specific entity subclass that matches the model type
|
|
281
|
+
specific entity subclass that matches the model type — searching the
|
|
282
|
+
whole subclass subtree, not just ``cls``'s direct children, so that a
|
|
283
|
+
polymorphic query through a *generic* STI root reaches the named
|
|
284
|
+
concrete subclasses rather than only the transparent ``Foo[X]``
|
|
285
|
+
parameterisations that share the root's automodel.
|
|
282
286
|
|
|
283
287
|
Args:
|
|
284
288
|
sa_model: A SQLAlchemy model instance to convert.
|
|
@@ -302,15 +306,18 @@ class SQLCrucibleEntity:
|
|
|
302
306
|
f"Hint: Make sure you're passing a SQLAlchemy model that was created from "
|
|
303
307
|
f"this entity class or one of its subclasses."
|
|
304
308
|
)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
else:
|
|
312
|
-
best_match = cls
|
|
309
|
+
sa_class = sa_model.__class__
|
|
310
|
+
|
|
311
|
+
def subtree(entity: type[Any]) -> Iterator[type[Any]]:
|
|
312
|
+
yield entity
|
|
313
|
+
for subclass in entity.__subclasses__():
|
|
314
|
+
yield from subtree(subclass)
|
|
313
315
|
|
|
316
|
+
best_match = min(
|
|
317
|
+
(entity for entity in subtree(cls) if issubclass(sa_class, entity.__sqlalchemy_type__)),
|
|
318
|
+
key=lambda entity: mro_distance(sa_class, entity.__sqlalchemy_type__),
|
|
319
|
+
default=cls,
|
|
320
|
+
)
|
|
314
321
|
return best_match._from_sa_model(sa_model)
|
|
315
322
|
|
|
316
323
|
@classmethod
|
|
@@ -27,7 +27,7 @@ from sqlcrucible.entity.field_definitions import (
|
|
|
27
27
|
canonicalise_typeform,
|
|
28
28
|
SQLCrucibleField,
|
|
29
29
|
)
|
|
30
|
-
from typing_extensions import get_annotations, Format
|
|
30
|
+
from typing_extensions import get_annotations, Format, TypeForm
|
|
31
31
|
|
|
32
32
|
if TYPE_CHECKING:
|
|
33
33
|
from sqlcrucible.entity.core import SQLCrucibleEntity
|
|
@@ -221,40 +221,23 @@ class ReadonlyFieldDescriptor(property, Generic[_T, _O]):
|
|
|
221
221
|
|
|
222
222
|
|
|
223
223
|
@overload
|
|
224
|
-
def readonly_field(tp:
|
|
224
|
+
def readonly_field(tp: TypeForm[_T]) -> _T: ...
|
|
225
225
|
|
|
226
226
|
|
|
227
227
|
@overload
|
|
228
|
-
def readonly_field(tp:
|
|
228
|
+
def readonly_field(tp: TypeForm[_T], arg1: SQLAlchemyField | ORMDescriptor[Any], /) -> _T: ...
|
|
229
229
|
|
|
230
230
|
|
|
231
231
|
@overload
|
|
232
232
|
def readonly_field(
|
|
233
|
-
tp:
|
|
233
|
+
tp: TypeForm[_T],
|
|
234
234
|
arg1: SQLAlchemyField | ORMDescriptor[Any],
|
|
235
235
|
arg2: SQLAlchemyField | ORMDescriptor[Any],
|
|
236
236
|
/,
|
|
237
237
|
) -> _T: ...
|
|
238
238
|
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
def readonly_field(tp: str) -> Any: ...
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
@overload
|
|
245
|
-
def readonly_field(tp: str, arg1: SQLAlchemyField | ORMDescriptor[Any], /) -> Any: ...
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
@overload
|
|
249
|
-
def readonly_field(
|
|
250
|
-
tp: str,
|
|
251
|
-
arg1: SQLAlchemyField | ORMDescriptor[Any],
|
|
252
|
-
arg2: SQLAlchemyField | ORMDescriptor[Any],
|
|
253
|
-
/,
|
|
254
|
-
) -> Any: ...
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def readonly_field(tp: type[_T] | str, *args: SQLAlchemyField | ORMDescriptor[Any]) -> Any:
|
|
240
|
+
def readonly_field(tp: TypeForm[_T], *args: SQLAlchemyField | ORMDescriptor[Any]) -> Any:
|
|
258
241
|
"""Create a readonly field descriptor.
|
|
259
242
|
|
|
260
243
|
Readonly fields are loaded from the SQLAlchemy model but cannot be set
|
|
@@ -262,7 +245,10 @@ def readonly_field(tp: type[_T] | str, *args: SQLAlchemyField | ORMDescriptor[An
|
|
|
262
245
|
and association_proxy.
|
|
263
246
|
|
|
264
247
|
Args:
|
|
265
|
-
tp: The type of the field value
|
|
248
|
+
tp: The type of the field value. Any type form is accepted and its
|
|
249
|
+
value type is preserved in the return type: a bare type (``str``),
|
|
250
|
+
a parameterized generic (``list[str]``), a union (``int | None``),
|
|
251
|
+
or a string forward reference (``"list[str]"``).
|
|
266
252
|
*args: Optional SQLAlchemyField and/or ORMDescriptor (e.g., hybrid_property,
|
|
267
253
|
association_proxy) in any order. If both are provided, the descriptor
|
|
268
254
|
is merged into the SQLAlchemyField. If neither is provided, the descriptor
|
|
@@ -39,21 +39,59 @@ def _group_by(iterable: Iterable[_T], key: Callable[[_T], _K]) -> dict[_K, list[
|
|
|
39
39
|
return groups
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
_STUB_SUFFIX = "-stubs"
|
|
43
|
+
|
|
44
|
+
|
|
42
45
|
def _stub_path(root: Path, module_name: str) -> Path:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
46
|
+
"""Map a dotted module to its file inside the ``<pkg>-stubs`` package.
|
|
47
|
+
|
|
48
|
+
The generated stubs override and extend the ``sqlcrucible`` package, so they
|
|
49
|
+
are emitted as a PEP 561 ``sqlcrucible-stubs`` partial stub package rather
|
|
50
|
+
than a parallel ``sqlcrucible`` tree. Type checkers (ty, pyright) resolve a
|
|
51
|
+
``<pkg>-stubs`` package independently of the installed package, which a
|
|
52
|
+
namespace tree in a separate search path no longer reliably does.
|
|
53
|
+
"""
|
|
54
|
+
top, *parts = module_name.split(".")
|
|
55
|
+
parts = [f"{top}{_STUB_SUFFIX}", *parts]
|
|
56
|
+
return root.joinpath(*parts[:-1], f"{parts[-1]}.pyi")
|
|
57
|
+
|
|
48
58
|
|
|
59
|
+
def _real_package_init(module_name: str) -> str | None:
|
|
60
|
+
"""Return the source of a real package's ``__init__`` module, if it exists.
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
Used to mirror re-exports into the stub package's ``__init__.pyi`` so the
|
|
63
|
+
empty stub does not shadow the runtime package's public API.
|
|
64
|
+
"""
|
|
52
65
|
try:
|
|
53
|
-
spec = importlib.util.find_spec(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
spec = importlib.util.find_spec(module_name)
|
|
67
|
+
except (ModuleNotFoundError, ValueError, ImportError):
|
|
68
|
+
return None
|
|
69
|
+
if spec is None or spec.submodule_search_locations is None or spec.origin is None:
|
|
70
|
+
return None
|
|
71
|
+
origin = Path(spec.origin)
|
|
72
|
+
return origin.read_text() if origin.name == "__init__.py" else None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _finalize_stub_package(output_dir: Path) -> None:
|
|
76
|
+
"""Mark each generated ``*-stubs`` tree as a PEP 561 partial stub package.
|
|
77
|
+
|
|
78
|
+
Type checkers only treat a ``<pkg>-stubs`` directory as a stub package when
|
|
79
|
+
every directory is an explicit package (has ``__init__.pyi``) and the
|
|
80
|
+
distribution is flagged partial (``py.typed`` containing ``partial``) so
|
|
81
|
+
modules absent from the stubs fall through to the runtime package. Each
|
|
82
|
+
``__init__.pyi`` mirrors the corresponding real package's ``__init__`` (when
|
|
83
|
+
one exists) so the stub does not shadow the runtime package's exports.
|
|
84
|
+
"""
|
|
85
|
+
for stub_package_dir in output_dir.glob(f"*{_STUB_SUFFIX}"):
|
|
86
|
+
(stub_package_dir / "py.typed").write_text("partial\n")
|
|
87
|
+
real_top = stub_package_dir.name[: -len(_STUB_SUFFIX)]
|
|
88
|
+
directories = [stub_package_dir, *(p for p in stub_package_dir.rglob("*") if p.is_dir())]
|
|
89
|
+
for directory in directories:
|
|
90
|
+
init_file = directory / "__init__.pyi"
|
|
91
|
+
if init_file.exists():
|
|
92
|
+
continue
|
|
93
|
+
module_name = ".".join((real_top, *directory.relative_to(stub_package_dir).parts))
|
|
94
|
+
init_file.write_text(_real_package_init(module_name) or "")
|
|
57
95
|
|
|
58
96
|
|
|
59
97
|
def _write_to_stub_file(classdefs: list[ClassDef], stubs_root: Path, module_name: str):
|
|
@@ -65,18 +103,6 @@ def _write_to_stub_file(classdefs: list[ClassDef], stubs_root: Path, module_name
|
|
|
65
103
|
stub_path = _stub_path(stubs_root, module_name)
|
|
66
104
|
stub_path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
105
|
|
|
68
|
-
# Create __init__.pyi files in parent directories that don't exist in source.
|
|
69
|
-
# For packages that exist in source, we use namespace packages (no __init__.pyi)
|
|
70
|
-
# so type checkers can merge stubs with the real source.
|
|
71
|
-
current = stubs_root
|
|
72
|
-
package_path = ""
|
|
73
|
-
for part in module_name.split(".")[:-1]:
|
|
74
|
-
current = current / part
|
|
75
|
-
package_path = f"{package_path}.{part}" if package_path else part
|
|
76
|
-
init_file = current / "__init__.pyi"
|
|
77
|
-
if not init_file.exists() and not _package_exists_in_source(package_path):
|
|
78
|
-
init_file.touch()
|
|
79
|
-
|
|
80
106
|
with open(stub_path, "w") as fd:
|
|
81
107
|
fd.write(import_block)
|
|
82
108
|
fd.write("\n\n")
|
|
@@ -146,3 +172,5 @@ def generate_stubs(
|
|
|
146
172
|
_generate_automodel_stubs(all_with_bases, output_path)
|
|
147
173
|
|
|
148
174
|
_generate_sa_type_stub(all_entities, output_path)
|
|
175
|
+
|
|
176
|
+
_finalize_stub_package(output_path)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Sequence
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
@@ -90,7 +91,9 @@ def construct_model_def(sqlalchemy_type: type) -> ClassDef:
|
|
|
90
91
|
)
|
|
91
92
|
|
|
92
93
|
|
|
93
|
-
def specificity_order(
|
|
94
|
+
def specificity_order(
|
|
95
|
+
entities: Sequence[type[SQLCrucibleEntity]],
|
|
96
|
+
) -> list[type[SQLCrucibleEntity]]:
|
|
94
97
|
"""Sort entities so subclasses appear before parent classes.
|
|
95
98
|
|
|
96
99
|
Type checkers match the first applicable overload, so more-specific
|
|
@@ -128,7 +131,7 @@ def _is_parameterised_generic(entity: type) -> bool:
|
|
|
128
131
|
return origin is not None and origin is not entity
|
|
129
132
|
|
|
130
133
|
|
|
131
|
-
def construct_sa_type_stub(entities:
|
|
134
|
+
def construct_sa_type_stub(entities: Sequence[type[SQLCrucibleEntity]]) -> str:
|
|
132
135
|
"""Construct a stub for SAType with @overload declarations.
|
|
133
136
|
|
|
134
137
|
Generates overloads on SATypeMeta.__getitem__ that map each entity
|
|
File without changes
|
|
@@ -100,6 +100,37 @@ def test_polymorphic_query_through_root_dispatches_to_subclass(engine):
|
|
|
100
100
|
assert isinstance(row, SAType[Single])
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
def test_from_sa_model_through_generic_root_yields_the_named_subclass(engine):
|
|
104
|
+
"""A heterogeneous polymorphic query through the generic STI root, fed
|
|
105
|
+
back through ``Release.from_sa_model`` (not ``Single``/``Album``
|
|
106
|
+
directly), yields the named concrete subclass for each row — with its
|
|
107
|
+
``details`` re-validated to that subclass's parameterised type. The
|
|
108
|
+
search has to walk past the transparent ``Release[SingleDetails]`` /
|
|
109
|
+
``Release[AlbumDetails]`` parameterisations (which share the root's
|
|
110
|
+
automodel) to reach ``Single`` / ``Album``."""
|
|
111
|
+
with Session(engine) as session:
|
|
112
|
+
session.add(
|
|
113
|
+
Single(details=SingleDetails(a_side="Heroes", b_side="V-2 Schneider")).to_sa_model()
|
|
114
|
+
)
|
|
115
|
+
session.add(
|
|
116
|
+
Album(
|
|
117
|
+
details=AlbumDetails(track_titles=["Speed of Life", "Breaking Glass"])
|
|
118
|
+
).to_sa_model()
|
|
119
|
+
)
|
|
120
|
+
session.commit()
|
|
121
|
+
|
|
122
|
+
rows = session.execute(select(SAType[Release])).scalars().all()
|
|
123
|
+
|
|
124
|
+
by_kind = {row.kind: Release.from_sa_model(row) for row in rows}
|
|
125
|
+
|
|
126
|
+
assert isinstance(by_kind["single"], Single)
|
|
127
|
+
assert by_kind["single"].details == SingleDetails(a_side="Heroes", b_side="V-2 Schneider")
|
|
128
|
+
assert isinstance(by_kind["album"], Album)
|
|
129
|
+
assert by_kind["album"].details == AlbumDetails(
|
|
130
|
+
track_titles=["Speed of Life", "Breaking Glass"]
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
103
134
|
def test_stub_generation_is_well_formed(tmp_path: Path):
|
|
104
135
|
"""No bracket-named automodel class is created, so the generated
|
|
105
136
|
stubs contain no class whose name has ``[`` ``]``; the parameterised
|
|
@@ -118,7 +149,7 @@ def test_stub_generation_is_well_formed(tmp_path: Path):
|
|
|
118
149
|
assert "Release[" not in source, f"{pyi} references a parameterised generic"
|
|
119
150
|
ast.parse(source, filename=str(pyi)) # SyntaxError on malformed output
|
|
120
151
|
|
|
121
|
-
sa_type_pyi = (tmp_path / "sqlcrucible" / "entity" / "sa_type.pyi").read_text()
|
|
152
|
+
sa_type_pyi = (tmp_path / "sqlcrucible-stubs" / "entity" / "sa_type.pyi").read_text()
|
|
122
153
|
# The concrete subclasses and the (abstract) origin do get overloads.
|
|
123
154
|
assert "tests.entity.generic_sti_models.Single]" in sa_type_pyi
|
|
124
155
|
assert "tests.entity.generic_sti_models.Album]" in sa_type_pyi
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Tests for stub file generation and error paths."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from sqlcrucible.stubs import _finalize_stub_package, _stub_path, generate_stubs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def stubs_root(tmp_path: Path) -> Path:
|
|
15
|
+
return tmp_path / "stubs"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize(
|
|
19
|
+
("module_name", "expected"),
|
|
20
|
+
[
|
|
21
|
+
("sqlcrucible.entity.sa_type", "sqlcrucible-stubs/entity/sa_type.pyi"),
|
|
22
|
+
("sqlcrucible.generated.myapp.models", "sqlcrucible-stubs/generated/myapp/models.pyi"),
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_stub_path_targets_stubs_package(stubs_root: Path, module_name: str, expected: str):
|
|
26
|
+
assert _stub_path(stubs_root, module_name) == stubs_root / expected
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_finalize_flags_package_partial_and_marks_stub_only_dirs(stubs_root: Path):
|
|
30
|
+
generated = stubs_root / "sqlcrucible-stubs" / "generated"
|
|
31
|
+
generated.mkdir(parents=True)
|
|
32
|
+
(generated / "models.pyi").write_text("class Fake: ...")
|
|
33
|
+
|
|
34
|
+
_finalize_stub_package(stubs_root)
|
|
35
|
+
|
|
36
|
+
assert (stubs_root / "sqlcrucible-stubs" / "py.typed").read_text() == "partial\n"
|
|
37
|
+
assert (generated / "__init__.pyi").read_text() == ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_finalize_mirrors_real_package_init_to_avoid_shadowing(stubs_root: Path):
|
|
41
|
+
(stubs_root / "sqlcrucible-stubs").mkdir(parents=True)
|
|
42
|
+
|
|
43
|
+
_finalize_stub_package(stubs_root)
|
|
44
|
+
|
|
45
|
+
init_pyi = (stubs_root / "sqlcrucible-stubs" / "__init__.pyi").read_text()
|
|
46
|
+
assert "SAType" in init_pyi
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_generate_stubs_no_entities_raises():
|
|
50
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
51
|
+
with pytest.raises(ValueError, match="No SQLCrucibleEntity subclasses found"):
|
|
52
|
+
generate_stubs(["json"], output_dir=tmpdir)
|
|
@@ -343,6 +343,7 @@ wheels = [
|
|
|
343
343
|
{ url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" },
|
|
344
344
|
{ url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" },
|
|
345
345
|
{ url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" },
|
|
346
|
+
{ url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" },
|
|
346
347
|
{ url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" },
|
|
347
348
|
{ url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" },
|
|
348
349
|
{ url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" },
|
|
@@ -350,6 +351,7 @@ wheels = [
|
|
|
350
351
|
{ url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" },
|
|
351
352
|
{ url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" },
|
|
352
353
|
{ url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" },
|
|
354
|
+
{ url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" },
|
|
353
355
|
{ url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" },
|
|
354
356
|
{ url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" },
|
|
355
357
|
{ url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" },
|
|
@@ -357,6 +359,7 @@ wheels = [
|
|
|
357
359
|
{ url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" },
|
|
358
360
|
{ url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" },
|
|
359
361
|
{ url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" },
|
|
362
|
+
{ url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" },
|
|
360
363
|
{ url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" },
|
|
361
364
|
{ url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" },
|
|
362
365
|
{ url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" },
|
|
@@ -364,6 +367,7 @@ wheels = [
|
|
|
364
367
|
{ url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" },
|
|
365
368
|
{ url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" },
|
|
366
369
|
{ url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" },
|
|
370
|
+
{ url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" },
|
|
367
371
|
{ url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" },
|
|
368
372
|
{ url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" },
|
|
369
373
|
{ url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" },
|
|
@@ -371,6 +375,7 @@ wheels = [
|
|
|
371
375
|
{ url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" },
|
|
372
376
|
{ url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" },
|
|
373
377
|
{ url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" },
|
|
378
|
+
{ url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" },
|
|
374
379
|
{ url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" },
|
|
375
380
|
{ url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" },
|
|
376
381
|
{ url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" },
|
|
@@ -1200,7 +1205,7 @@ dev = [
|
|
|
1200
1205
|
{ name = "pytest", specifier = ">=8.0.0" },
|
|
1201
1206
|
{ name = "pytest-cov", specifier = ">=6.0.0" },
|
|
1202
1207
|
{ name = "ruff" },
|
|
1203
|
-
{ name = "ty" },
|
|
1208
|
+
{ name = "ty", specifier = ">=0.0.41" },
|
|
1204
1209
|
]
|
|
1205
1210
|
docs = [
|
|
1206
1211
|
{ name = "mike", specifier = ">=2.1" },
|
|
@@ -1213,11 +1218,11 @@ test = [
|
|
|
1213
1218
|
{ name = "pyright" },
|
|
1214
1219
|
{ name = "pytest", specifier = ">=8.0.0" },
|
|
1215
1220
|
{ name = "pytest-cov", specifier = ">=6.0.0" },
|
|
1216
|
-
{ name = "ty" },
|
|
1221
|
+
{ name = "ty", specifier = ">=0.0.41" },
|
|
1217
1222
|
]
|
|
1218
1223
|
typecheck = [
|
|
1219
1224
|
{ name = "pyright" },
|
|
1220
|
-
{ name = "ty" },
|
|
1225
|
+
{ name = "ty", specifier = ">=0.0.41" },
|
|
1221
1226
|
]
|
|
1222
1227
|
|
|
1223
1228
|
[[package]]
|
|
@@ -1276,27 +1281,27 @@ wheels = [
|
|
|
1276
1281
|
|
|
1277
1282
|
[[package]]
|
|
1278
1283
|
name = "ty"
|
|
1279
|
-
version = "0.0.
|
|
1280
|
-
source = { registry = "https://pypi.org/simple" }
|
|
1281
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1282
|
-
wheels = [
|
|
1283
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1284
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1285
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1286
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1287
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1288
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1289
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1290
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1291
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1292
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1293
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1294
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1295
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1296
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1297
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1298
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1299
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1284
|
+
version = "0.0.49"
|
|
1285
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1286
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1d/8d/37cb91808069509d43a2a11743e12f1e854fd808dbef2203309d256718cd/ty-0.0.49.tar.gz", hash = "sha256:0a027bd0c9c75d035641a365d087ad883446057f9be0b9826251c2aecafbf145", size = 5884753, upload-time = "2026-06-12T03:08:20.221Z" }
|
|
1287
|
+
wheels = [
|
|
1288
|
+
{ url = "https://files.pythonhosted.org/packages/ca/de/9237c6a96356612dd0393db1e94cf21f903616adf3a3701bf3da6e4adc92/ty-0.0.49-py3-none-linux_armv6l.whl", hash = "sha256:12c0c4310b936d762a8586c210b53d4fa4bb361a04429afa89bf84b922e5e065", size = 11834671, upload-time = "2026-06-12T03:07:53.062Z" },
|
|
1289
|
+
{ url = "https://files.pythonhosted.org/packages/8f/15/daf5a14a5e07012277d450c75325c94614e2acfec4c620c881486118c410/ty-0.0.49-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:737bfdc2caf9712a8580944dcdc80a450a37a4f2bc83c8fa9b7433b374f9e471", size = 11589570, upload-time = "2026-06-12T03:08:25.779Z" },
|
|
1290
|
+
{ url = "https://files.pythonhosted.org/packages/7d/58/30bdf98436488aca25f0763bf7f92a061528d42461b686453029e845e4c5/ty-0.0.49-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ab90c1baf3b1701d282fce4b02fa552a962d109f8972c46ef6b22429503bfea4", size = 10985236, upload-time = "2026-06-12T03:08:36.664Z" },
|
|
1291
|
+
{ url = "https://files.pythonhosted.org/packages/22/45/ece503e4a1396e13a1a9a0cde51afe476a6506a1d557eeadf8ad45c83bc0/ty-0.0.49-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ce8ecf6ba6fc79bd137cc0557a754f7e5f2dfe9436412551d480d680e248ad", size = 11504302, upload-time = "2026-06-12T03:08:01.664Z" },
|
|
1292
|
+
{ url = "https://files.pythonhosted.org/packages/17/dc/5d09333d289dfbca1804eaade125c9e8a1a992a2a592a8b80c5e9b589ca9/ty-0.0.49-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10d85c6865c984e78661e0bd20b180514b4a289739224e84816e342bdf381e04", size = 11626629, upload-time = "2026-06-12T03:08:06.844Z" },
|
|
1293
|
+
{ url = "https://files.pythonhosted.org/packages/f2/36/155f41c9dd7237c4b609211f29f77755a139ee6218605dadc7fe21d5e3c8/ty-0.0.49-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d96a67a206619e01fa92f35a22267ec634bba62be24b1d0e947020cc179995b", size = 12074481, upload-time = "2026-06-12T03:08:09.643Z" },
|
|
1294
|
+
{ url = "https://files.pythonhosted.org/packages/96/4c/998ee13cd5045f1f8b36982de7343163832ac53f27debe01b0de0e8bd968/ty-0.0.49-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de9f648564e0a66344ef397770387cb0d093735f8679d2c5a08a4741e79814d", size = 12678042, upload-time = "2026-06-12T03:08:39.319Z" },
|
|
1295
|
+
{ url = "https://files.pythonhosted.org/packages/85/c9/9a505aba85c41ce54cbcaa14f8d79aa084b86151d2d70df11c4655b92898/ty-0.0.49-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5779179ab397d15f8c9dbb8f506ec1b1745f54eac639982f76ef3ce538943b50", size = 12316194, upload-time = "2026-06-12T03:08:18.023Z" },
|
|
1296
|
+
{ url = "https://files.pythonhosted.org/packages/c9/b8/ded37fb93503294abbc83c36470bb1413bea05048b745881d4470b518a06/ty-0.0.49-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:792d4974e93cc09bd32f934586080bbbe21b8e777099cb521cb2de18b68a49f0", size = 12145507, upload-time = "2026-06-12T03:07:56.505Z" },
|
|
1297
|
+
{ url = "https://files.pythonhosted.org/packages/2f/07/392e80d78f02445f695b815bb9eb0fffacda68b03faee38c900f7b990815/ty-0.0.49-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:727bda86deb136073e525c2e78d60e38aedcce5d80579170844a52bbf7c1440d", size = 12365967, upload-time = "2026-06-12T03:08:12.553Z" },
|
|
1298
|
+
{ url = "https://files.pythonhosted.org/packages/50/d3/31b0c2a7fbedd3373e389cb1d81b8d2128f6f868fafb46557736a6f9aca8/ty-0.0.49-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4f2fc2bc4a8d2ff1cca59fd94772cabdfec4062d47a0b3a0784be46d94d0540b", size = 11475283, upload-time = "2026-06-12T03:08:28.334Z" },
|
|
1299
|
+
{ url = "https://files.pythonhosted.org/packages/5a/5b/329e101638920b468a3bb63059c9f66ef99b44aac501222c44832a507321/ty-0.0.49-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3724bd9badef333321578b6a941fbc571ebf49141ec2356a8590fbe4c9aa588d", size = 11645343, upload-time = "2026-06-12T03:08:15.246Z" },
|
|
1300
|
+
{ url = "https://files.pythonhosted.org/packages/a9/76/c897e615e32f80ca81c8c1bc49b9a1f72ff9e3cfea0f8345ba505fe28472/ty-0.0.49-py3-none-musllinux_1_2_i686.whl", hash = "sha256:166c6eb52ee4af3c5a9bb267d165d93000daa55c6758cd8ff3199741fb75917d", size = 11725585, upload-time = "2026-06-12T03:08:33.915Z" },
|
|
1301
|
+
{ url = "https://files.pythonhosted.org/packages/59/e1/fdb42ee239f618800842681af5bb8598117e74512c10974a8b7b9086a898/ty-0.0.49-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:91e81d832c287b05782ee32eb1b801f62c1fa08df37d589d2b88c3f1d51c9731", size = 12237261, upload-time = "2026-06-12T03:08:31.105Z" },
|
|
1302
|
+
{ url = "https://files.pythonhosted.org/packages/98/0f/a2d6a5fc9d0786cbeb3c200786da4e18c203589be3984bb5def83ca92320/ty-0.0.49-py3-none-win32.whl", hash = "sha256:7186af5ca9829d1f5d8916bcf767b8e819bfbf61b1b8ec843bb3fc699cb502e1", size = 11100789, upload-time = "2026-06-12T03:07:59.092Z" },
|
|
1303
|
+
{ url = "https://files.pythonhosted.org/packages/d0/9d/473ac8bc57b5a2d121da893bf9dd74a118efb19a01d711df1a6e397f05cc/ty-0.0.49-py3-none-win_amd64.whl", hash = "sha256:ae2142fc126a01effcca0c222908b0e6654b5ba1266d4e4d406e4866aef8e1d1", size = 12204644, upload-time = "2026-06-12T03:08:04.327Z" },
|
|
1304
|
+
{ url = "https://files.pythonhosted.org/packages/ef/a2/8959249da951ba3977fee20e688d28678b8a1d30a9ed4464228a85d45853/ty-0.0.49-py3-none-win_arm64.whl", hash = "sha256:75d5e2e7649765f31f4bed6c8adb149a75b18edd3fa6336dac4d0efc1a66466f", size = 11558965, upload-time = "2026-06-12T03:08:23.012Z" },
|
|
1300
1305
|
]
|
|
1301
1306
|
|
|
1302
1307
|
[[package]]
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"""Tests for stub file generation and error paths."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import tempfile
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
from sqlcrucible.stubs import _write_to_stub_file, generate_stubs
|
|
11
|
-
from sqlcrucible.stubs.codegen import ClassDef
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _stub_classdef(module: str) -> ClassDef:
|
|
15
|
-
return ClassDef(source=object, module=module, imports=[], class_def="class Fake: pass")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@pytest.fixture
|
|
19
|
-
def stubs_root(tmp_path: Path) -> Path:
|
|
20
|
-
return tmp_path / "stubs"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_write_stub_creates_init_pyi_for_nonexistent_package(stubs_root: Path):
|
|
24
|
-
module_name = "nonexistent.fake.module"
|
|
25
|
-
_write_to_stub_file([_stub_classdef(module_name)], stubs_root, module_name)
|
|
26
|
-
|
|
27
|
-
for package in ("nonexistent", "nonexistent/fake"):
|
|
28
|
-
init_pyi = stubs_root / package / "__init__.pyi"
|
|
29
|
-
assert init_pyi.exists(), f"Expected {init_pyi} to exist for non-source package"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def test_write_stub_skips_init_pyi_for_source_package(stubs_root: Path):
|
|
33
|
-
module_name = "sqlcrucible.stubs.fakefile"
|
|
34
|
-
_write_to_stub_file([_stub_classdef(module_name)], stubs_root, module_name)
|
|
35
|
-
|
|
36
|
-
for package in ("sqlcrucible", "sqlcrucible/stubs"):
|
|
37
|
-
init_pyi = stubs_root / package / "__init__.pyi"
|
|
38
|
-
assert not init_pyi.exists(), f"Expected {init_pyi} to NOT exist for source package"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_generate_stubs_no_entities_raises():
|
|
42
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
43
|
-
with pytest.raises(ValueError, match="No SQLCrucibleEntity subclasses found"):
|
|
44
|
-
generate_stubs(["json"], output_dir=tmpdir)
|
|
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
|
|
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
|
{sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/orm_descriptors/test_association_proxy.py
RENAMED
|
File without changes
|
{sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/orm_descriptors/test_hybrid_property.py
RENAMED
|
File without changes
|
{sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/orm_descriptors/test_writable_descriptors.py
RENAMED
|
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
|
{sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_annotated_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_one_to_many_child.py
RENAMED
|
File without changes
|
{sqlcrucible-0.4.0 → sqlcrucible-0.5.0}/tests/entity/test_relationships_one_to_many_parent.py
RENAMED
|
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
|