etlantic-sqlmodel 0.14.0__tar.gz → 0.18.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.
@@ -20,6 +20,8 @@ htmlcov/
20
20
  # macOS
21
21
  .DS_Store
22
22
  **/.DS_Store
23
+ ._*
24
+ **/._*
23
25
 
24
26
  examples/_file_storage_out/
25
27
  examples/_generated_*.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlantic-sqlmodel
3
- Version: 0.14.0
3
+ Version: 0.18.0
4
4
  Summary: ContractModel ↔ SQLModel bridge for ETLantic.
5
5
  Project-URL: Homepage, https://github.com/eddiethedean/etlantic
6
6
  Project-URL: Documentation, https://github.com/eddiethedean/etlantic/tree/main/docs
@@ -9,7 +9,7 @@ Project-URL: Issues, https://github.com/eddiethedean/etlantic/issues
9
9
  Project-URL: Changelog, https://github.com/eddiethedean/etlantic/blob/main/CHANGELOG.md
10
10
  Author-email: Odo Matthews <odosmatthews@gmail.com>
11
11
  License-Expression: MIT
12
- Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Programming Language :: Python :: 3
@@ -18,17 +18,17 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Typing :: Typed
20
20
  Requires-Python: >=3.11
21
- Requires-Dist: etlantic<0.15,>=0.14.0
21
+ Requires-Dist: etlantic<0.19,>=0.18.0
22
22
  Requires-Dist: sqlmodel<1,>=0.0.22
23
23
  Description-Content-Type: text/markdown
24
24
 
25
25
  # etlantic-sqlmodel
26
26
 
27
27
  Optional bridge between ETLantic `Data` contracts and
28
- [SQLModel](https://sqlmodel.tiangolo.com/) table models for ETLantic 0.14.
28
+ [SQLModel](https://sqlmodel.tiangolo.com/) table models for ETLantic 0.18.
29
29
 
30
30
  ```bash
31
- pip install 'etlantic==0.14.0' 'etlantic-sqlmodel==0.14.0'
31
+ pip install 'etlantic==0.18.0' 'etlantic-sqlmodel==0.18.0'
32
32
  # or: pip install 'etlantic[sqlmodel]'
33
33
  ```
34
34
 
@@ -1,10 +1,10 @@
1
1
  # etlantic-sqlmodel
2
2
 
3
3
  Optional bridge between ETLantic `Data` contracts and
4
- [SQLModel](https://sqlmodel.tiangolo.com/) table models for ETLantic 0.14.
4
+ [SQLModel](https://sqlmodel.tiangolo.com/) table models for ETLantic 0.18.
5
5
 
6
6
  ```bash
7
- pip install 'etlantic==0.14.0' 'etlantic-sqlmodel==0.14.0'
7
+ pip install 'etlantic==0.18.0' 'etlantic-sqlmodel==0.18.0'
8
8
  # or: pip install 'etlantic[sqlmodel]'
9
9
  ```
10
10
 
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "etlantic-sqlmodel"
3
- version = "0.14.0"
3
+ version = "0.18.0"
4
4
  description = "ContractModel ↔ SQLModel bridge for ETLantic."
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
7
  requires-python = ">=3.11"
8
8
  authors = [{ name = "Odo Matthews", email = "odosmatthews@gmail.com" }]
9
9
  classifiers = [
10
- "Development Status :: 3 - Alpha",
10
+ "Development Status :: 5 - Production/Stable",
11
11
  "Intended Audience :: Developers",
12
12
  "License :: OSI Approved :: MIT License",
13
13
  "Programming Language :: Python :: 3",
@@ -17,7 +17,7 @@ classifiers = [
17
17
  "Typing :: Typed",
18
18
  ]
19
19
  dependencies = [
20
- "etlantic>=0.14.0,<0.15",
20
+ "etlantic>=0.18.0,<0.19",
21
21
  "sqlmodel>=0.0.22,<1",
22
22
  ]
23
23
 
@@ -20,13 +20,14 @@ from etlantic.schema_drift import (
20
20
  )
21
21
  from sqlmodel import Field, SQLModel
22
22
 
23
- __version__ = "0.14.0"
23
+ __version__ = "0.18.0"
24
24
 
25
25
  __all__ = [
26
26
  "SqlModelIntegrationPlugin",
27
27
  "__version__",
28
28
  "compare_metadata",
29
29
  "contract_to_sqlmodel",
30
+ "contract_to_sqlmodel_source",
30
31
  "create_plugin",
31
32
  "run_conformance_checks",
32
33
  "sqlmodel_to_contract",
@@ -82,6 +83,99 @@ def contract_to_sqlmodel(
82
83
  return SQLModelMetaclass(model_name, (SQLModel,), attrs, table=True)
83
84
 
84
85
 
86
+ def contract_to_sqlmodel_source(
87
+ contract_cls: type[Data],
88
+ *,
89
+ table_name: str | None = None,
90
+ primary_key: tuple[str, ...] | None = None,
91
+ ) -> str:
92
+ """Generate importable SQLModel source for a ``Data`` / ContractModel class."""
93
+ if not is_data_contract_type(contract_cls):
94
+ raise TypeError("Expected a Data / ContractModel subclass")
95
+
96
+ resolved_table = table_name or _default_table_name(contract_cls)
97
+ pk_fields = set(primary_key or ())
98
+ model_name = f"{contract_cls.__name__}Table"
99
+ imports: set[str] = set()
100
+ field_lines: list[str] = []
101
+
102
+ for name, field_info in contract_cls.model_fields.items():
103
+ python_type = _python_type_from_annotation(field_info.annotation)
104
+ type_name, extra_imports = _annotation_source(python_type, optional=False)
105
+ imports.update(extra_imports)
106
+ optional = not field_info.is_required()
107
+ if optional:
108
+ type_name = f"{type_name} | None"
109
+ if name in pk_fields:
110
+ field_lines.append(f" {name}: {type_name} = Field(primary_key=True)")
111
+ elif field_info.default is not ...:
112
+ default_repr = _default_source(field_info.default, imports)
113
+ field_lines.append(
114
+ f" {name}: {type_name} = Field(default={default_repr})"
115
+ )
116
+ elif optional:
117
+ field_lines.append(f" {name}: {type_name} = Field(default=None)")
118
+ else:
119
+ field_lines.append(f" {name}: {type_name}")
120
+
121
+ import_lines = ["from sqlmodel import Field, SQLModel"]
122
+ if "datetime" in imports:
123
+ import_lines.insert(0, "from datetime import datetime")
124
+ if "date" in imports:
125
+ import_lines.insert(0, "from datetime import date")
126
+ if "Decimal" in imports:
127
+ import_lines.insert(0, "from decimal import Decimal")
128
+ # De-duplicate datetime imports when both date and datetime needed
129
+ if "datetime" in imports and "date" in imports:
130
+ import_lines = [
131
+ line
132
+ for line in import_lines
133
+ if line
134
+ not in {"from datetime import date", "from datetime import datetime"}
135
+ ]
136
+ import_lines.insert(0, "from datetime import date, datetime")
137
+
138
+ body = "\n".join(field_lines) if field_lines else " pass"
139
+ return (
140
+ "\n".join(import_lines)
141
+ + "\n\n\n"
142
+ + f"class {model_name}(SQLModel, table=True):\n"
143
+ + f' __tablename__ = "{resolved_table}"\n'
144
+ + f"{body}\n"
145
+ )
146
+
147
+
148
+ def _annotation_source(
149
+ python_type: type[Any], *, optional: bool
150
+ ) -> tuple[str, set[str]]:
151
+ _ = optional
152
+ mapping = {
153
+ str: ("str", set()),
154
+ int: ("int", set()),
155
+ float: ("float", set()),
156
+ bool: ("bool", set()),
157
+ datetime: ("datetime", {"datetime"}),
158
+ date: ("date", {"date"}),
159
+ Decimal: ("Decimal", {"Decimal"}),
160
+ }
161
+ if python_type in mapping:
162
+ return mapping[python_type]
163
+ return ("str", set())
164
+
165
+
166
+ def _default_source(value: Any, imports: set[str]) -> str:
167
+ if isinstance(value, Decimal):
168
+ imports.add("Decimal")
169
+ return f"Decimal({str(value)!r})"
170
+ if isinstance(value, datetime):
171
+ imports.add("datetime")
172
+ return repr(value)
173
+ if isinstance(value, date):
174
+ imports.add("date")
175
+ return repr(value)
176
+ return repr(value)
177
+
178
+
85
179
  def sqlmodel_to_contract(model_cls: type[Any]) -> dict[str, Any]:
86
180
  """Extract draft contract metadata from a SQLModel table class."""
87
181
  if not _is_sqlmodel_table(model_cls):