pycharter 0.0.20__py3-none-any.whl → 0.0.22__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.
- api/dependencies/__init__.py +2 -1
- api/dependencies/database.py +71 -5
- api/main.py +47 -8
- api/models/contracts.py +6 -4
- api/models/metadata.py +11 -7
- api/models/schemas.py +16 -10
- api/routes/v1/contracts.py +498 -226
- api/routes/v1/metadata.py +52 -211
- api/routes/v1/schemas.py +1 -1
- api/routes/v1/settings.py +88 -1
- api/utils.py +224 -0
- pycharter/__init__.py +149 -93
- pycharter/data/templates/template_transform_advanced.yaml +50 -0
- pycharter/data/templates/template_transform_simple.yaml +59 -0
- pycharter/db/models/base.py +1 -2
- pycharter/etl_generator/orchestrator.py +463 -487
- pycharter/metadata_store/postgres.py +16 -191
- pycharter/metadata_store/sqlite.py +12 -41
- {pycharter-0.0.20.dist-info → pycharter-0.0.22.dist-info}/METADATA +284 -62
- pycharter-0.0.22.dist-info/RECORD +358 -0
- ui/static/404/index.html +1 -1
- ui/static/404.html +1 -1
- ui/static/__next.__PAGE__.txt +1 -1
- ui/static/__next._full.txt +2 -2
- ui/static/__next._head.txt +1 -1
- ui/static/__next._index.txt +2 -2
- ui/static/__next._tree.txt +2 -2
- ui/static/_next/static/chunks/13d4a0fbd74c1ee4.js +1 -0
- ui/static/_next/static/chunks/2edb43b48432ac04.js +441 -0
- ui/static/_next/static/chunks/c4fa4f4114b7c352.js +1 -0
- ui/static/_next/static/chunks/d2363397e1b2bcab.css +1 -0
- ui/static/_next/static/chunks/f7d1a90dd75d2572.js +1 -0
- ui/static/_not-found/__next._full.txt +2 -2
- ui/static/_not-found/__next._head.txt +1 -1
- ui/static/_not-found/__next._index.txt +2 -2
- ui/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
- ui/static/_not-found/__next._not-found.txt +1 -1
- ui/static/_not-found/__next._tree.txt +2 -2
- ui/static/_not-found/index.html +1 -1
- ui/static/_not-found/index.txt +2 -2
- ui/static/contracts/__next._full.txt +3 -3
- ui/static/contracts/__next._head.txt +1 -1
- ui/static/contracts/__next._index.txt +2 -2
- ui/static/contracts/__next._tree.txt +2 -2
- ui/static/contracts/__next.contracts.__PAGE__.txt +2 -2
- ui/static/contracts/__next.contracts.txt +1 -1
- ui/static/contracts/index.html +1 -1
- ui/static/contracts/index.txt +3 -3
- ui/static/documentation/__next._full.txt +3 -3
- ui/static/documentation/__next._head.txt +1 -1
- ui/static/documentation/__next._index.txt +2 -2
- ui/static/documentation/__next._tree.txt +2 -2
- ui/static/documentation/__next.documentation.__PAGE__.txt +2 -2
- ui/static/documentation/__next.documentation.txt +1 -1
- ui/static/documentation/index.html +2 -2
- ui/static/documentation/index.txt +3 -3
- ui/static/index.html +1 -1
- ui/static/index.txt +2 -2
- ui/static/metadata/__next._full.txt +2 -2
- ui/static/metadata/__next._head.txt +1 -1
- ui/static/metadata/__next._index.txt +2 -2
- ui/static/metadata/__next._tree.txt +2 -2
- ui/static/metadata/__next.metadata.__PAGE__.txt +1 -1
- ui/static/metadata/__next.metadata.txt +1 -1
- ui/static/metadata/index.html +1 -1
- ui/static/metadata/index.txt +2 -2
- ui/static/quality/__next._full.txt +2 -2
- ui/static/quality/__next._head.txt +1 -1
- ui/static/quality/__next._index.txt +2 -2
- ui/static/quality/__next._tree.txt +2 -2
- ui/static/quality/__next.quality.__PAGE__.txt +1 -1
- ui/static/quality/__next.quality.txt +1 -1
- ui/static/quality/index.html +2 -2
- ui/static/quality/index.txt +2 -2
- ui/static/rules/__next._full.txt +2 -2
- ui/static/rules/__next._head.txt +1 -1
- ui/static/rules/__next._index.txt +2 -2
- ui/static/rules/__next._tree.txt +2 -2
- ui/static/rules/__next.rules.__PAGE__.txt +1 -1
- ui/static/rules/__next.rules.txt +1 -1
- ui/static/rules/index.html +1 -1
- ui/static/rules/index.txt +2 -2
- ui/static/schemas/__next._full.txt +2 -2
- ui/static/schemas/__next._head.txt +1 -1
- ui/static/schemas/__next._index.txt +2 -2
- ui/static/schemas/__next._tree.txt +2 -2
- ui/static/schemas/__next.schemas.__PAGE__.txt +1 -1
- ui/static/schemas/__next.schemas.txt +1 -1
- ui/static/schemas/index.html +1 -1
- ui/static/schemas/index.txt +2 -2
- ui/static/settings/__next._full.txt +2 -2
- ui/static/settings/__next._head.txt +1 -1
- ui/static/settings/__next._index.txt +2 -2
- ui/static/settings/__next._tree.txt +2 -2
- ui/static/settings/__next.settings.__PAGE__.txt +1 -1
- ui/static/settings/__next.settings.txt +1 -1
- ui/static/settings/index.html +1 -1
- ui/static/settings/index.txt +2 -2
- ui/static/static/.gitkeep +0 -0
- ui/static/static/404/index.html +1 -0
- ui/static/static/404.html +1 -0
- ui/static/static/__next.__PAGE__.txt +10 -0
- ui/static/static/__next._full.txt +30 -0
- ui/static/static/__next._head.txt +7 -0
- ui/static/static/__next._index.txt +9 -0
- ui/static/static/__next._tree.txt +2 -0
- ui/static/static/_next/static/chunks/222442f6da32302a.js +1 -0
- ui/static/static/_next/static/chunks/247eb132b7f7b574.js +1 -0
- ui/static/static/_next/static/chunks/297d55555b71baba.js +1 -0
- ui/static/static/_next/static/chunks/2ab439ce003cd691.js +1 -0
- ui/static/static/_next/static/chunks/414e77373f8ff61c.js +1 -0
- ui/static/static/_next/static/chunks/49ca65abd26ae49e.js +1 -0
- ui/static/static/_next/static/chunks/5e04d10c4a7b58a3.js +1 -0
- ui/static/static/_next/static/chunks/652ad0aa26265c47.js +2 -0
- ui/static/static/_next/static/chunks/75d88a058d8ffaa6.js +1 -0
- ui/static/static/_next/static/chunks/8c89634cf6bad76f.js +1 -0
- ui/static/static/_next/static/chunks/9667e7a3d359eb39.js +1 -0
- ui/static/static/_next/static/chunks/9c23f44fff36548a.js +1 -0
- ui/static/static/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- ui/static/static/_next/static/chunks/b32a0963684b9933.js +4 -0
- ui/static/static/_next/static/chunks/c69f6cba366bd988.js +1 -0
- ui/static/static/_next/static/chunks/db913959c675cea6.js +1 -0
- ui/static/static/_next/static/chunks/f061a4be97bfc3b3.js +1 -0
- ui/static/static/_next/static/chunks/f2e7afeab1178138.js +1 -0
- ui/static/static/_next/static/chunks/ff1a16fafef87110.js +1 -0
- ui/static/static/_next/static/chunks/turbopack-ffcb7ab6794027ef.js +3 -0
- ui/static/static/_next/static/tNTkVW6puVXC4bAm4WrHl/_buildManifest.js +11 -0
- ui/static/static/_next/static/tNTkVW6puVXC4bAm4WrHl/_ssgManifest.js +1 -0
- ui/static/static/_not-found/__next._full.txt +17 -0
- ui/static/static/_not-found/__next._head.txt +7 -0
- ui/static/static/_not-found/__next._index.txt +9 -0
- ui/static/static/_not-found/__next._not-found.__PAGE__.txt +5 -0
- ui/static/static/_not-found/__next._not-found.txt +4 -0
- ui/static/static/_not-found/__next._tree.txt +2 -0
- ui/static/static/_not-found/index.html +1 -0
- ui/static/static/_not-found/index.txt +17 -0
- ui/static/static/contracts/__next._full.txt +21 -0
- ui/static/static/contracts/__next._head.txt +7 -0
- ui/static/static/contracts/__next._index.txt +9 -0
- ui/static/static/contracts/__next._tree.txt +2 -0
- ui/static/static/contracts/__next.contracts.__PAGE__.txt +9 -0
- ui/static/static/contracts/__next.contracts.txt +4 -0
- ui/static/static/contracts/index.html +1 -0
- ui/static/static/contracts/index.txt +21 -0
- ui/static/static/documentation/__next._full.txt +21 -0
- ui/static/static/documentation/__next._head.txt +7 -0
- ui/static/static/documentation/__next._index.txt +9 -0
- ui/static/static/documentation/__next._tree.txt +2 -0
- ui/static/static/documentation/__next.documentation.__PAGE__.txt +9 -0
- ui/static/static/documentation/__next.documentation.txt +4 -0
- ui/static/static/documentation/index.html +93 -0
- ui/static/static/documentation/index.txt +21 -0
- ui/static/static/index.html +1 -0
- ui/static/static/index.txt +30 -0
- ui/static/static/metadata/__next._full.txt +21 -0
- ui/static/static/metadata/__next._head.txt +7 -0
- ui/static/static/metadata/__next._index.txt +9 -0
- ui/static/static/metadata/__next._tree.txt +2 -0
- ui/static/static/metadata/__next.metadata.__PAGE__.txt +9 -0
- ui/static/static/metadata/__next.metadata.txt +4 -0
- ui/static/static/metadata/index.html +1 -0
- ui/static/static/metadata/index.txt +21 -0
- ui/static/static/quality/__next._full.txt +21 -0
- ui/static/static/quality/__next._head.txt +7 -0
- ui/static/static/quality/__next._index.txt +9 -0
- ui/static/static/quality/__next._tree.txt +2 -0
- ui/static/static/quality/__next.quality.__PAGE__.txt +9 -0
- ui/static/static/quality/__next.quality.txt +4 -0
- ui/static/static/quality/index.html +2 -0
- ui/static/static/quality/index.txt +21 -0
- ui/static/static/rules/__next._full.txt +21 -0
- ui/static/static/rules/__next._head.txt +7 -0
- ui/static/static/rules/__next._index.txt +9 -0
- ui/static/static/rules/__next._tree.txt +2 -0
- ui/static/static/rules/__next.rules.__PAGE__.txt +9 -0
- ui/static/static/rules/__next.rules.txt +4 -0
- ui/static/static/rules/index.html +1 -0
- ui/static/static/rules/index.txt +21 -0
- ui/static/static/schemas/__next._full.txt +21 -0
- ui/static/static/schemas/__next._head.txt +7 -0
- ui/static/static/schemas/__next._index.txt +9 -0
- ui/static/static/schemas/__next._tree.txt +2 -0
- ui/static/static/schemas/__next.schemas.__PAGE__.txt +9 -0
- ui/static/static/schemas/__next.schemas.txt +4 -0
- ui/static/static/schemas/index.html +1 -0
- ui/static/static/schemas/index.txt +21 -0
- ui/static/static/settings/__next._full.txt +21 -0
- ui/static/static/settings/__next._head.txt +7 -0
- ui/static/static/settings/__next._index.txt +9 -0
- ui/static/static/settings/__next._tree.txt +2 -0
- ui/static/static/settings/__next.settings.__PAGE__.txt +9 -0
- ui/static/static/settings/__next.settings.txt +4 -0
- ui/static/static/settings/index.html +1 -0
- ui/static/static/settings/index.txt +21 -0
- ui/static/static/validation/__next._full.txt +21 -0
- ui/static/static/validation/__next._head.txt +7 -0
- ui/static/static/validation/__next._index.txt +9 -0
- ui/static/static/validation/__next._tree.txt +2 -0
- ui/static/static/validation/__next.validation.__PAGE__.txt +9 -0
- ui/static/static/validation/__next.validation.txt +4 -0
- ui/static/static/validation/index.html +1 -0
- ui/static/static/validation/index.txt +21 -0
- ui/static/validation/__next._full.txt +2 -2
- ui/static/validation/__next._head.txt +1 -1
- ui/static/validation/__next._index.txt +2 -2
- ui/static/validation/__next._tree.txt +2 -2
- ui/static/validation/__next.validation.__PAGE__.txt +1 -1
- ui/static/validation/__next.validation.txt +1 -1
- ui/static/validation/index.html +1 -1
- ui/static/validation/index.txt +2 -2
- pycharter/db/schemas/.ipynb_checkpoints/data_contract-checkpoint.py +0 -160
- pycharter-0.0.20.dist-info/RECORD +0 -247
- {pycharter-0.0.20.dist-info → pycharter-0.0.22.dist-info}/WHEEL +0 -0
- {pycharter-0.0.20.dist-info → pycharter-0.0.22.dist-info}/entry_points.txt +0 -0
- {pycharter-0.0.20.dist-info → pycharter-0.0.22.dist-info}/licenses/LICENSE +0 -0
- {pycharter-0.0.20.dist-info → pycharter-0.0.22.dist-info}/top_level.txt +0 -0
- /ui/static/_next/static/{tNTkVW6puVXC4bAm4WrHl → 0rYA78L88aUyD2Uh38hhX}/_buildManifest.js +0 -0
- /ui/static/_next/static/{tNTkVW6puVXC4bAm4WrHl → 0rYA78L88aUyD2Uh38hhX}/_ssgManifest.js +0 -0
- /ui/static/{_next → static/_next}/static/chunks/4e310fe5005770a3.css +0 -0
- /ui/static/{_next → static/_next}/static/chunks/5fc14c00a2779dc5.js +0 -0
- /ui/static/{_next → static/_next}/static/chunks/b584574fdc8ab13e.js +0 -0
- /ui/static/{_next → static/_next}/static/chunks/d5989c94d3614b3a.js +0 -0
api/utils.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared utilities for API routes.
|
|
3
|
+
|
|
4
|
+
This module provides common helper functions used across API endpoints,
|
|
5
|
+
including UUID handling, database queries, and error handling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Optional, TypeVar, Union
|
|
11
|
+
|
|
12
|
+
from fastapi import HTTPException, status
|
|
13
|
+
from sqlalchemy import cast, String
|
|
14
|
+
from sqlalchemy.orm import Session
|
|
15
|
+
from sqlalchemy.orm.decl_api import DeclarativeMeta
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def safe_uuid_to_str(value: Optional[Union[uuid.UUID, str]]) -> Optional[str]:
|
|
23
|
+
"""
|
|
24
|
+
Safely convert a UUID or string to a string representation.
|
|
25
|
+
|
|
26
|
+
Handles both UUID objects and string representations, including
|
|
27
|
+
cases where SQLite might store UUIDs as strings.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
value: UUID object, string, or None
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
String representation of UUID, or None if value is None
|
|
34
|
+
"""
|
|
35
|
+
if value is None:
|
|
36
|
+
return None
|
|
37
|
+
if isinstance(value, uuid.UUID):
|
|
38
|
+
return str(value)
|
|
39
|
+
if isinstance(value, str):
|
|
40
|
+
# If it's already a string, validate it's a valid UUID format
|
|
41
|
+
try:
|
|
42
|
+
# Validate and normalize the UUID string
|
|
43
|
+
uuid_obj = uuid.UUID(value)
|
|
44
|
+
return str(uuid_obj)
|
|
45
|
+
except (ValueError, AttributeError):
|
|
46
|
+
# If it's not a valid UUID, return as-is (might be a schema name)
|
|
47
|
+
return value
|
|
48
|
+
# Fallback: convert to string
|
|
49
|
+
return str(value)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def ensure_uuid(value: Optional[Union[uuid.UUID, str, bytes]]) -> Optional[uuid.UUID]:
|
|
53
|
+
"""
|
|
54
|
+
Ensure a value is a UUID object, converting from string or bytes if needed.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
value: UUID object, string, bytes, or None
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
UUID object, or None if value is None
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: If value cannot be converted to UUID
|
|
64
|
+
"""
|
|
65
|
+
if value is None:
|
|
66
|
+
return None
|
|
67
|
+
if isinstance(value, uuid.UUID):
|
|
68
|
+
return value
|
|
69
|
+
if isinstance(value, str):
|
|
70
|
+
# Handle empty strings
|
|
71
|
+
if not value.strip():
|
|
72
|
+
return None
|
|
73
|
+
return uuid.UUID(value)
|
|
74
|
+
if isinstance(value, bytes):
|
|
75
|
+
# Handle bytes (sometimes database drivers return UUIDs as bytes)
|
|
76
|
+
return uuid.UUID(value.decode('utf-8'))
|
|
77
|
+
# Try to convert to string first, then to UUID
|
|
78
|
+
try:
|
|
79
|
+
return uuid.UUID(str(value))
|
|
80
|
+
except (ValueError, AttributeError) as e:
|
|
81
|
+
raise ValueError(f"Cannot convert {type(value)} ({value}) to UUID: {e}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_by_id_with_fallback(
|
|
85
|
+
db: Session,
|
|
86
|
+
model: DeclarativeMeta,
|
|
87
|
+
id_value: Union[uuid.UUID, str],
|
|
88
|
+
error_message: Optional[str] = None,
|
|
89
|
+
) -> Optional[T]:
|
|
90
|
+
"""
|
|
91
|
+
Get a database record by ID with fallback strategies for UUID compatibility.
|
|
92
|
+
|
|
93
|
+
This function tries multiple strategies to find a record:
|
|
94
|
+
1. Direct UUID comparison
|
|
95
|
+
2. String comparison (for SQLite compatibility)
|
|
96
|
+
3. Query all and compare by string (final fallback)
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
db: Database session
|
|
100
|
+
model: SQLAlchemy model class
|
|
101
|
+
id_value: UUID or string ID to search for
|
|
102
|
+
error_message: Optional custom error message if not found
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Model instance if found, None otherwise
|
|
106
|
+
"""
|
|
107
|
+
# Try UUID comparison first
|
|
108
|
+
try:
|
|
109
|
+
if isinstance(id_value, str):
|
|
110
|
+
id_uuid = uuid.UUID(id_value)
|
|
111
|
+
else:
|
|
112
|
+
id_uuid = id_value
|
|
113
|
+
|
|
114
|
+
record = db.query(model).filter(model.id == id_uuid).first()
|
|
115
|
+
if record:
|
|
116
|
+
return record
|
|
117
|
+
except (ValueError, TypeError):
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
# Try string comparison (for SQLite compatibility)
|
|
121
|
+
id_str = safe_uuid_to_str(id_value)
|
|
122
|
+
if id_str:
|
|
123
|
+
try:
|
|
124
|
+
record = db.query(model).filter(
|
|
125
|
+
cast(model.id, String) == id_str
|
|
126
|
+
).first()
|
|
127
|
+
if record:
|
|
128
|
+
return record
|
|
129
|
+
except Exception:
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
# Final fallback: query all and compare by string
|
|
133
|
+
try:
|
|
134
|
+
all_records = db.query(model).all()
|
|
135
|
+
for record in all_records:
|
|
136
|
+
record_id_str = safe_uuid_to_str(record.id)
|
|
137
|
+
if record_id_str and id_str:
|
|
138
|
+
if (record_id_str == id_str or
|
|
139
|
+
record_id_str.lower() == id_str.lower()):
|
|
140
|
+
return record
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.debug(f"Fallback query failed: {e}")
|
|
143
|
+
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_by_id_or_404(
|
|
148
|
+
db: Session,
|
|
149
|
+
model: DeclarativeMeta,
|
|
150
|
+
id_value: Union[uuid.UUID, str],
|
|
151
|
+
error_message: Optional[str] = None,
|
|
152
|
+
model_name: Optional[str] = None,
|
|
153
|
+
) -> T:
|
|
154
|
+
"""
|
|
155
|
+
Get a database record by ID or raise 404 if not found.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
db: Database session
|
|
159
|
+
model: SQLAlchemy model class
|
|
160
|
+
id_value: UUID or string ID to search for
|
|
161
|
+
error_message: Optional custom error message
|
|
162
|
+
model_name: Optional model name for error message
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Model instance
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
HTTPException: 404 if record not found
|
|
169
|
+
"""
|
|
170
|
+
record = get_by_id_with_fallback(db, model, id_value, error_message)
|
|
171
|
+
|
|
172
|
+
if not record:
|
|
173
|
+
id_str = safe_uuid_to_str(id_value)
|
|
174
|
+
model_display = model_name or model.__name__
|
|
175
|
+
default_message = f"{model_display} with ID {id_str} not found"
|
|
176
|
+
message = error_message or default_message
|
|
177
|
+
|
|
178
|
+
# Log diagnostic info
|
|
179
|
+
total = db.query(model).count()
|
|
180
|
+
logger.error(
|
|
181
|
+
f"{model_display} not found: {id_str}. "
|
|
182
|
+
f"Total {model_display.lower()}s: {total}"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
raise HTTPException(
|
|
186
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
187
|
+
detail=message,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return record
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def model_to_dict(model_instance) -> dict:
|
|
194
|
+
"""
|
|
195
|
+
Convert SQLAlchemy model instance to dictionary.
|
|
196
|
+
|
|
197
|
+
Handles UUIDs, datetimes, and JSON fields appropriately.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
model_instance: SQLAlchemy model instance
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Dictionary representation of the model
|
|
204
|
+
"""
|
|
205
|
+
if model_instance is None:
|
|
206
|
+
return {}
|
|
207
|
+
|
|
208
|
+
result = {}
|
|
209
|
+
for column in model_instance.__table__.columns:
|
|
210
|
+
value = getattr(model_instance, column.name, None)
|
|
211
|
+
if value is None:
|
|
212
|
+
result[column.name] = None
|
|
213
|
+
# Convert UUID to string
|
|
214
|
+
elif hasattr(value, 'hex'):
|
|
215
|
+
result[column.name] = str(value)
|
|
216
|
+
# Convert datetime to ISO format string
|
|
217
|
+
elif hasattr(value, 'isoformat'):
|
|
218
|
+
result[column.name] = value.isoformat()
|
|
219
|
+
# Keep JSON fields as-is
|
|
220
|
+
elif isinstance(value, (dict, list)):
|
|
221
|
+
result[column.name] = value
|
|
222
|
+
else:
|
|
223
|
+
result[column.name] = value
|
|
224
|
+
return result
|
pycharter/__init__.py
CHANGED
|
@@ -1,35 +1,109 @@
|
|
|
1
1
|
"""
|
|
2
2
|
PyCharter - Data Contract Management and Validation
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Core Services:
|
|
5
5
|
1. Contract Parser - Reads and decomposes data contract files
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
2. Contract Builder - Constructs consolidated contracts from separate artifacts
|
|
7
|
+
3. Metadata Store - Database operations for metadata storage
|
|
8
|
+
4. Pydantic Generator - Generates Pydantic models from JSON Schema
|
|
9
|
+
5. JSON Schema Converter - Converts Pydantic models to JSON Schema
|
|
10
|
+
6. Runtime Validator - Validation utilities
|
|
11
|
+
7. Quality Assurance - Data quality checking and monitoring
|
|
12
|
+
|
|
13
|
+
API Organization:
|
|
14
|
+
- Tier 1: Primary Interfaces (Classes - Use these for best performance)
|
|
15
|
+
- Tier 2: Convenience Functions (Quick start - one-off use cases)
|
|
16
|
+
- Tier 3: Low-Level Utilities (When you already have models/schemas)
|
|
11
17
|
"""
|
|
12
18
|
|
|
13
|
-
__version__ = "0.0.
|
|
19
|
+
__version__ = "0.0.22"
|
|
14
20
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
# ============================================================================
|
|
22
|
+
# TIER 1: PRIMARY INTERFACES (Classes - Use these for best performance)
|
|
23
|
+
# ============================================================================
|
|
24
|
+
|
|
25
|
+
# Runtime Validator (PRIMARY INTERFACE)
|
|
26
|
+
from pycharter.runtime_validator import (
|
|
27
|
+
Validator, # ⭐ PRIMARY: Use this for validation
|
|
28
|
+
create_validator,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Quality Assurance (PRIMARY INTERFACE)
|
|
32
|
+
from pycharter.quality import (
|
|
33
|
+
QualityCheck, # ⭐ PRIMARY: Use this for quality checks
|
|
34
|
+
QualityCheckOptions,
|
|
35
|
+
QualityReport,
|
|
36
|
+
QualityThresholds,
|
|
20
37
|
)
|
|
21
38
|
|
|
22
|
-
#
|
|
39
|
+
# Metadata Store (PRIMARY INTERFACE)
|
|
40
|
+
from pycharter.metadata_store import MetadataStoreClient
|
|
41
|
+
|
|
42
|
+
# ============================================================================
|
|
43
|
+
# TIER 2: CONVENIENCE FUNCTIONS (Quick start - one-off use cases)
|
|
44
|
+
# ============================================================================
|
|
45
|
+
|
|
46
|
+
# Contract Parser
|
|
23
47
|
from pycharter.contract_parser import (
|
|
24
48
|
ContractMetadata,
|
|
25
49
|
parse_contract,
|
|
26
50
|
parse_contract_file,
|
|
27
51
|
)
|
|
28
52
|
|
|
29
|
-
#
|
|
30
|
-
from pycharter.
|
|
53
|
+
# Contract Builder
|
|
54
|
+
from pycharter.contract_builder import (
|
|
55
|
+
ContractArtifacts,
|
|
56
|
+
build_contract,
|
|
57
|
+
build_contract_from_store,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Pydantic Generator - Input type helpers (convenience)
|
|
61
|
+
from pycharter.pydantic_generator import (
|
|
62
|
+
from_dict, # Quick: schema from dict
|
|
63
|
+
from_file, # Quick: schema from file
|
|
64
|
+
from_json, # Quick: schema from JSON string
|
|
65
|
+
from_url, # Quick: schema from URL
|
|
66
|
+
generate_model, # Advanced: when you need more control
|
|
67
|
+
generate_model_file,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# JSON Schema Converter - Output type helpers (convenience)
|
|
71
|
+
from pycharter.json_schema_converter import (
|
|
72
|
+
to_dict, # Quick: schema to dict
|
|
73
|
+
to_file, # Quick: schema to file
|
|
74
|
+
to_json, # Quick: schema to JSON string
|
|
75
|
+
model_to_schema, # Advanced: core conversion
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Runtime Validator - Data source helpers (convenience)
|
|
79
|
+
from pycharter.runtime_validator import (
|
|
80
|
+
ValidationResult,
|
|
81
|
+
# Quick validation functions (use Validator class for multiple validations)
|
|
82
|
+
validate_with_store, # Quick: validate with metadata store
|
|
83
|
+
validate_batch_with_store, # Quick: batch validate with metadata store
|
|
84
|
+
validate_with_contract, # Quick: validate with contract file/dict
|
|
85
|
+
validate_batch_with_contract, # Quick: batch validate with contract file/dict
|
|
86
|
+
get_model_from_store, # Quick: get model from store
|
|
87
|
+
get_model_from_contract, # Quick: get model from contract
|
|
88
|
+
# Decorators
|
|
89
|
+
validate_input,
|
|
90
|
+
validate_output,
|
|
91
|
+
validate_with_contract_decorator,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# ============================================================================
|
|
95
|
+
# TIER 3: LOW-LEVEL UTILITIES (When you already have models/schemas)
|
|
96
|
+
# ============================================================================
|
|
97
|
+
|
|
98
|
+
from pycharter.runtime_validator import (
|
|
99
|
+
validate, # Low-level: validate with existing model
|
|
100
|
+
validate_batch, # Low-level: batch validate with existing model
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# ============================================================================
|
|
104
|
+
# METADATA STORE IMPLEMENTATIONS
|
|
105
|
+
# ============================================================================
|
|
31
106
|
|
|
32
|
-
# Optional metadata store implementations
|
|
33
107
|
try:
|
|
34
108
|
from pycharter.metadata_store import InMemoryMetadataStore
|
|
35
109
|
except ImportError:
|
|
@@ -50,104 +124,86 @@ try:
|
|
|
50
124
|
except ImportError:
|
|
51
125
|
RedisMetadataStore = None # type: ignore[assignment,misc]
|
|
52
126
|
|
|
53
|
-
|
|
54
|
-
from pycharter.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
to_file,
|
|
58
|
-
to_json,
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
# Service 3: Pydantic Generator
|
|
62
|
-
from pycharter.pydantic_generator import (
|
|
63
|
-
from_dict,
|
|
64
|
-
from_file,
|
|
65
|
-
from_json,
|
|
66
|
-
from_url,
|
|
67
|
-
generate_model,
|
|
68
|
-
generate_model_file,
|
|
69
|
-
)
|
|
127
|
+
try:
|
|
128
|
+
from pycharter.metadata_store import SQLiteMetadataStore
|
|
129
|
+
except ImportError:
|
|
130
|
+
SQLiteMetadataStore = None # type: ignore[assignment,misc]
|
|
70
131
|
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
get_model_from_contract,
|
|
75
|
-
get_model_from_store,
|
|
76
|
-
validate,
|
|
77
|
-
validate_batch,
|
|
78
|
-
validate_batch_with_contract,
|
|
79
|
-
validate_batch_with_store,
|
|
80
|
-
validate_input,
|
|
81
|
-
validate_output,
|
|
82
|
-
validate_with_contract,
|
|
83
|
-
validate_with_contract_decorator,
|
|
84
|
-
validate_with_store,
|
|
85
|
-
)
|
|
132
|
+
# ============================================================================
|
|
133
|
+
# QUALITY ASSURANCE - Additional utilities (if needed for advanced use)
|
|
134
|
+
# ============================================================================
|
|
86
135
|
|
|
87
|
-
# Service 6: Quality Assurance
|
|
88
136
|
from pycharter.quality import (
|
|
89
|
-
DataProfiler,
|
|
90
|
-
FieldQualityMetrics,
|
|
91
|
-
QualityCheck,
|
|
92
|
-
QualityCheckOptions,
|
|
93
137
|
QualityMetrics,
|
|
94
|
-
QualityReport,
|
|
95
138
|
QualityScore,
|
|
96
|
-
|
|
97
|
-
ViolationRecord,
|
|
139
|
+
FieldQualityMetrics,
|
|
98
140
|
ViolationTracker,
|
|
141
|
+
ViolationRecord,
|
|
142
|
+
DataProfiler,
|
|
99
143
|
)
|
|
100
144
|
|
|
101
145
|
__all__ = [
|
|
102
|
-
#
|
|
146
|
+
# ========================================================================
|
|
147
|
+
# TIER 1: PRIMARY INTERFACES
|
|
148
|
+
# ========================================================================
|
|
149
|
+
"Validator", # ⭐ PRIMARY: Use for validation
|
|
150
|
+
"create_validator",
|
|
151
|
+
"QualityCheck", # ⭐ PRIMARY: Use for quality checks
|
|
152
|
+
"QualityCheckOptions",
|
|
153
|
+
"QualityReport",
|
|
154
|
+
"QualityThresholds",
|
|
155
|
+
"MetadataStoreClient",
|
|
156
|
+
# ========================================================================
|
|
157
|
+
# TIER 2: CONVENIENCE FUNCTIONS
|
|
158
|
+
# ========================================================================
|
|
159
|
+
# Contract Management
|
|
103
160
|
"parse_contract",
|
|
104
161
|
"parse_contract_file",
|
|
105
162
|
"ContractMetadata",
|
|
106
|
-
# Contract Builder
|
|
107
163
|
"build_contract",
|
|
108
164
|
"build_contract_from_store",
|
|
109
165
|
"ContractArtifacts",
|
|
110
|
-
#
|
|
111
|
-
"
|
|
166
|
+
# Pydantic Generator (input type helpers)
|
|
167
|
+
"from_dict", # Quick: dict → model
|
|
168
|
+
"from_file", # Quick: file → model
|
|
169
|
+
"from_json", # Quick: JSON string → model
|
|
170
|
+
"from_url", # Quick: URL → model
|
|
171
|
+
"generate_model", # Advanced: more control
|
|
172
|
+
"generate_model_file",
|
|
173
|
+
# JSON Schema Converter (output type helpers)
|
|
174
|
+
"to_dict", # Quick: model → dict
|
|
175
|
+
"to_file", # Quick: model → file
|
|
176
|
+
"to_json", # Quick: model → JSON string
|
|
177
|
+
"model_to_schema", # Advanced: core conversion
|
|
178
|
+
# Runtime Validator (data source helpers)
|
|
179
|
+
"ValidationResult",
|
|
180
|
+
"validate_with_store", # Quick: store → validate
|
|
181
|
+
"validate_batch_with_store", # Quick: batch validate with store
|
|
182
|
+
"validate_with_contract", # Quick: contract → validate
|
|
183
|
+
"validate_batch_with_contract", # Quick: batch validate with contract
|
|
184
|
+
"get_model_from_store", # Quick: store → model
|
|
185
|
+
"get_model_from_contract", # Quick: contract → model
|
|
186
|
+
"validate_input", # Decorator
|
|
187
|
+
"validate_output", # Decorator
|
|
188
|
+
"validate_with_contract_decorator",
|
|
189
|
+
# ========================================================================
|
|
190
|
+
# TIER 3: LOW-LEVEL UTILITIES
|
|
191
|
+
# ========================================================================
|
|
192
|
+
"validate", # Low-level: model → validate
|
|
193
|
+
"validate_batch", # Low-level: model → batch validate
|
|
194
|
+
# ========================================================================
|
|
195
|
+
# METADATA STORE IMPLEMENTATIONS
|
|
196
|
+
# ========================================================================
|
|
112
197
|
"InMemoryMetadataStore",
|
|
113
198
|
"MongoDBMetadataStore",
|
|
114
199
|
"PostgresMetadataStore",
|
|
115
200
|
"RedisMetadataStore",
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
"from_file",
|
|
121
|
-
"from_json",
|
|
122
|
-
"from_url",
|
|
123
|
-
# JSON Schema Converter
|
|
124
|
-
"to_dict",
|
|
125
|
-
"to_file",
|
|
126
|
-
"to_json",
|
|
127
|
-
"model_to_schema",
|
|
128
|
-
# Runtime Validator
|
|
129
|
-
"validate",
|
|
130
|
-
"validate_batch",
|
|
131
|
-
"ValidationResult",
|
|
132
|
-
# Database-backed validation
|
|
133
|
-
"validate_with_store",
|
|
134
|
-
"validate_batch_with_store",
|
|
135
|
-
"get_model_from_store",
|
|
136
|
-
# Contract-based validation (no database)
|
|
137
|
-
"validate_with_contract",
|
|
138
|
-
"validate_batch_with_contract",
|
|
139
|
-
"get_model_from_contract",
|
|
140
|
-
# Decorators
|
|
141
|
-
"validate_input",
|
|
142
|
-
"validate_output",
|
|
143
|
-
"validate_with_contract_decorator",
|
|
144
|
-
# Quality Assurance
|
|
145
|
-
"QualityCheck",
|
|
201
|
+
"SQLiteMetadataStore",
|
|
202
|
+
# ========================================================================
|
|
203
|
+
# QUALITY ASSURANCE - Additional utilities
|
|
204
|
+
# ========================================================================
|
|
146
205
|
"QualityMetrics",
|
|
147
206
|
"QualityScore",
|
|
148
|
-
"QualityReport",
|
|
149
|
-
"QualityThresholds",
|
|
150
|
-
"QualityCheckOptions",
|
|
151
207
|
"FieldQualityMetrics",
|
|
152
208
|
"ViolationTracker",
|
|
153
209
|
"ViolationRecord",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Advanced Transformation Template
|
|
2
|
+
# Use this template for complex transformations with JSONata and custom functions
|
|
3
|
+
|
|
4
|
+
title: advanced_transformation
|
|
5
|
+
description: Advanced transformation with JSONata and custom functions
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
|
|
8
|
+
# Step 1: Simple operations (optional but recommended for basic cleanup)
|
|
9
|
+
transform:
|
|
10
|
+
rename:
|
|
11
|
+
oldName: new_name
|
|
12
|
+
convert:
|
|
13
|
+
price: float
|
|
14
|
+
timestamp: datetime
|
|
15
|
+
defaults:
|
|
16
|
+
source: "api"
|
|
17
|
+
|
|
18
|
+
# Step 2: JSONata for complex transformations
|
|
19
|
+
jsonata:
|
|
20
|
+
expression: |
|
|
21
|
+
$.{
|
|
22
|
+
"ticker": symbol,
|
|
23
|
+
"price_change": price - previousClose,
|
|
24
|
+
"price_change_pct": ((price - previousClose) / previousClose) * 100,
|
|
25
|
+
"is_positive": price > previousClose,
|
|
26
|
+
"formatted_date": $fromMillis(timestamp),
|
|
27
|
+
"metadata": {
|
|
28
|
+
"source": source,
|
|
29
|
+
"processed_at": $now()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
mode: "record" # Apply to each record individually
|
|
33
|
+
# mode: "batch" # Apply to entire dataset
|
|
34
|
+
|
|
35
|
+
# Step 3: Custom Python function for business logic
|
|
36
|
+
custom_function:
|
|
37
|
+
# Option 1: Using module and function
|
|
38
|
+
module: "myproject.transforms"
|
|
39
|
+
function: "optimize_portfolio"
|
|
40
|
+
mode: "batch"
|
|
41
|
+
kwargs:
|
|
42
|
+
method: "min_volatility"
|
|
43
|
+
solver: "ipopt"
|
|
44
|
+
constraints:
|
|
45
|
+
max_weight: 0.1
|
|
46
|
+
min_weight: 0.01
|
|
47
|
+
|
|
48
|
+
# Option 2: Using callable path (alternative)
|
|
49
|
+
# callable: "myproject.transforms.optimize_portfolio"
|
|
50
|
+
# mode: "batch"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Simple Transformation Template
|
|
2
|
+
# Use this template for basic field transformations
|
|
3
|
+
|
|
4
|
+
title: simple_transformation
|
|
5
|
+
description: Simple transformation configuration
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
|
|
8
|
+
# Simple operations (applied in order: rename → convert → defaults → add → select → drop)
|
|
9
|
+
transform:
|
|
10
|
+
# Rename fields (old_name: new_name)
|
|
11
|
+
rename:
|
|
12
|
+
oldName: new_name
|
|
13
|
+
camelCase: snake_case
|
|
14
|
+
userId: user_id
|
|
15
|
+
|
|
16
|
+
# Convert field types
|
|
17
|
+
convert:
|
|
18
|
+
price: float
|
|
19
|
+
quantity: integer
|
|
20
|
+
active: boolean
|
|
21
|
+
created_at: datetime
|
|
22
|
+
|
|
23
|
+
# Set default values for missing fields
|
|
24
|
+
defaults:
|
|
25
|
+
status: "pending"
|
|
26
|
+
priority: 0
|
|
27
|
+
|
|
28
|
+
# Add computed fields
|
|
29
|
+
add:
|
|
30
|
+
full_name: "${first_name} ${last_name}"
|
|
31
|
+
created_at: "now()"
|
|
32
|
+
record_id: "uuid()"
|
|
33
|
+
|
|
34
|
+
# Keep only these fields (removes all others)
|
|
35
|
+
# select:
|
|
36
|
+
# - field1
|
|
37
|
+
# - field2
|
|
38
|
+
|
|
39
|
+
# Remove these fields (keeps all others)
|
|
40
|
+
# drop:
|
|
41
|
+
# - internal_id
|
|
42
|
+
# - debug_info
|
|
43
|
+
|
|
44
|
+
# Advanced: JSONata for complex transformations (optional)
|
|
45
|
+
# jsonata:
|
|
46
|
+
# expression: |
|
|
47
|
+
# $.{
|
|
48
|
+
# "ticker": symbol,
|
|
49
|
+
# "avg_price": $average(prices)
|
|
50
|
+
# }
|
|
51
|
+
# mode: "record" # or "batch"
|
|
52
|
+
|
|
53
|
+
# Advanced: Custom Python function (optional)
|
|
54
|
+
# custom_function:
|
|
55
|
+
# module: "myproject.transforms"
|
|
56
|
+
# function: "optimize_data"
|
|
57
|
+
# mode: "batch"
|
|
58
|
+
# kwargs:
|
|
59
|
+
# method: "min_volatility"
|
pycharter/db/models/base.py
CHANGED
|
@@ -3,8 +3,7 @@ Base SQLAlchemy configuration
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from sqlalchemy import create_engine, event
|
|
6
|
-
from sqlalchemy.
|
|
7
|
-
from sqlalchemy.orm import sessionmaker
|
|
6
|
+
from sqlalchemy.orm import declarative_base, sessionmaker
|
|
8
7
|
from sqlalchemy.schema import CreateTable
|
|
9
8
|
|
|
10
9
|
Base = declarative_base()
|