teaql 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {teaql-0.2.0 → teaql-0.2.2}/PKG-INFO +1 -1
- {teaql-0.2.0 → teaql-0.2.2}/pyproject.toml +1 -1
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/__init__.py +5 -2
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/context.py +143 -3
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/i18n.py +3 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/PKG-INFO +1 -1
- {teaql-0.2.0 → teaql-0.2.2}/LICENSE +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/README.md +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/setup.cfg +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/entity.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/eval.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/expr.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/graph.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/list.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/meta.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/mutation.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/query.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/safe_expression.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/value.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/xls.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/data_service/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/mysql/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/mysql/dialect.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/mysql/transport.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/postgres/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/postgres/dialect.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/postgres/transport.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/redis/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/redis/remote.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/redis/store.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/sqlite/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/sqlite/dialect.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/sqlite/transport.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/tfp_client/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/py.typed +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/_schema_capability.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/audit.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/builtin-messages-v1.json +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/env.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/module.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/opentelemetry.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/store.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/telemetry.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/tools.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/sql/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/sql/dialect.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/sql/executor.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/sql/types.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/web_integration/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql/web_integration/fastapi/__init__.py +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/SOURCES.txt +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/dependency_links.txt +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/requires.txt +0 -0
- {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/top_level.txt +0 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
from .context import
|
|
1
|
+
from .context import (
|
|
2
|
+
ContextEntityRef, ContextRootError, DiagnosticSqlLogSink, UserContext,
|
|
3
|
+
TeaqlRuntime, SqlLogEntry, SqlLogOperation, TextDiagnosticSqlLogSink,
|
|
4
|
+
)
|
|
2
5
|
from .tools import (
|
|
3
6
|
ContextTools, ExecutableHttpTool, HTTP_TOOL, HttpIntentPhase, HttpTool,
|
|
4
7
|
HttpToolProvider, ToolDeniedError, ToolError, ToolPolicy, ToolRisk,
|
|
@@ -10,7 +13,7 @@ from .audit import RawAuditEvent, SafeAuditEvent, MutationAuditKind
|
|
|
10
13
|
from .i18n import CheckException, CheckResult, I18nCatalog, Locale, ObjectLocation, UnsupportedLocaleError
|
|
11
14
|
from teaql.core.entity import EntityKey, EntityChangeSet, EntityRoot
|
|
12
15
|
|
|
13
|
-
__all__ = ["EntityKey", "EntityChangeSet", "EntityRoot", "ContextEntityRef", "ContextRootError", "CheckException", "CheckResult", "I18nCatalog", "Locale", "ObjectLocation", "UnsupportedLocaleError", "UserContext", "TeaqlRuntime", "SqlLogEntry", "SqlLogOperation", "ServiceRuntimeFromEnv", "RuntimeModule", "DataStore", "RawAuditEvent", "SafeAuditEvent", "MutationAuditKind", "ContextTools", "ExecutableHttpTool", "HTTP_TOOL", "HttpIntentPhase", "HttpTool", "HttpToolProvider", "ToolDeniedError", "ToolError", "ToolPolicy", "ToolRisk", "Tools", "ToolToken", "ToolUnavailableError"]
|
|
16
|
+
__all__ = ["EntityKey", "EntityChangeSet", "EntityRoot", "ContextEntityRef", "ContextRootError", "CheckException", "CheckResult", "I18nCatalog", "Locale", "ObjectLocation", "UnsupportedLocaleError", "UserContext", "TeaqlRuntime", "SqlLogEntry", "SqlLogOperation", "DiagnosticSqlLogSink", "TextDiagnosticSqlLogSink", "ServiceRuntimeFromEnv", "RuntimeModule", "DataStore", "RawAuditEvent", "SafeAuditEvent", "MutationAuditKind", "ContextTools", "ExecutableHttpTool", "HTTP_TOOL", "HttpIntentPhase", "HttpTool", "HttpToolProvider", "ToolDeniedError", "ToolError", "ToolPolicy", "ToolRisk", "Tools", "ToolToken", "ToolUnavailableError"]
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
def __getattr__(name):
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from typing import Dict, Any, Optional, List, Callable, TypeVar
|
|
2
|
+
import asyncio
|
|
3
|
+
import contextvars
|
|
2
4
|
from dataclasses import dataclass
|
|
3
5
|
from array import array
|
|
4
6
|
|
|
@@ -11,6 +13,13 @@ class ContextEntityRef:
|
|
|
11
13
|
entity: str
|
|
12
14
|
id: int
|
|
13
15
|
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class FixEvidence:
|
|
18
|
+
entity_type: str
|
|
19
|
+
model_path: str
|
|
20
|
+
source: str
|
|
21
|
+
source_label: str
|
|
22
|
+
|
|
14
23
|
class ContextRootError(Exception):
|
|
15
24
|
def __init__(self, reason: str, expected_type: str, active_root: Optional[ContextEntityRef] = None):
|
|
16
25
|
self.reason = reason
|
|
@@ -84,6 +93,35 @@ class UserContext:
|
|
|
84
93
|
self._id_set_plan = "ID_SET_DISABLED"
|
|
85
94
|
self._id_set_count = 0
|
|
86
95
|
self._id_set_count_accuracy = "UNKNOWN"
|
|
96
|
+
self._graph_save_active = False
|
|
97
|
+
self._graph_save_lock = asyncio.Lock()
|
|
98
|
+
self._graph_save_owner = contextvars.ContextVar(
|
|
99
|
+
f"teaql_graph_save_owner_{id(self)}", default=None
|
|
100
|
+
)
|
|
101
|
+
self._graph_commit_actions: List[Any] = []
|
|
102
|
+
self._graph_rollback_actions: List[Any] = []
|
|
103
|
+
self._fix_evidence_current: List[FixEvidence] = []
|
|
104
|
+
self._fix_evidence_last: List[FixEvidence] = []
|
|
105
|
+
|
|
106
|
+
def begin_fix_evidence(self):
|
|
107
|
+
self._fix_evidence_current = []
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def record_fix_evidence(self, entity_type: str, model_path: str, source: str, source_label: str):
|
|
111
|
+
normalized = source_label.lower()
|
|
112
|
+
if not entity_type or not model_path or source not in ("clock", "context") or not source_label \
|
|
113
|
+
or "authorization" in normalized or "cookie" in normalized or "token=" in normalized:
|
|
114
|
+
raise ValueError("Fix evidence must contain only safe framework provenance labels")
|
|
115
|
+
self._fix_evidence_current.append(FixEvidence(entity_type, model_path, source, source_label))
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
def finish_fix_evidence(self):
|
|
119
|
+
self._fix_evidence_last = list(self._fix_evidence_current)
|
|
120
|
+
self._fix_evidence_current = []
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
def last_fix_evidence(self):
|
|
124
|
+
return tuple(self._fix_evidence_last)
|
|
87
125
|
|
|
88
126
|
@classmethod
|
|
89
127
|
def new(cls) -> 'UserContext':
|
|
@@ -103,6 +141,74 @@ class UserContext:
|
|
|
103
141
|
raise ContextRootError("type_mismatch", expected_type, root)
|
|
104
142
|
return root
|
|
105
143
|
|
|
144
|
+
async def execute_graph_save(self, work):
|
|
145
|
+
"""Run one generated entity graph in one provider transaction."""
|
|
146
|
+
if self._graph_save_owner.get() is not None:
|
|
147
|
+
return await work()
|
|
148
|
+
async with self._graph_save_lock:
|
|
149
|
+
provider = self.require_resource("dataService")
|
|
150
|
+
begin = getattr(provider, "begin", None)
|
|
151
|
+
if not callable(begin):
|
|
152
|
+
raise RuntimeError("Configured dataService does not support graph transactions")
|
|
153
|
+
try:
|
|
154
|
+
transaction = await begin(self)
|
|
155
|
+
except TypeError:
|
|
156
|
+
transaction = await begin()
|
|
157
|
+
owner_token = self._graph_save_owner.set(object())
|
|
158
|
+
self._graph_save_active = True
|
|
159
|
+
self._graph_commit_actions = []
|
|
160
|
+
self._graph_rollback_actions = []
|
|
161
|
+
self.insert_resource("fix_time", datetime.now())
|
|
162
|
+
self.begin_fix_evidence()
|
|
163
|
+
self.insert_resource("dataService", transaction)
|
|
164
|
+
try:
|
|
165
|
+
result = await work()
|
|
166
|
+
except BaseException:
|
|
167
|
+
try:
|
|
168
|
+
await self._finish_graph_transaction(transaction, "rollback")
|
|
169
|
+
finally:
|
|
170
|
+
for action in reversed(self._graph_rollback_actions):
|
|
171
|
+
action()
|
|
172
|
+
raise
|
|
173
|
+
else:
|
|
174
|
+
try:
|
|
175
|
+
await self._finish_graph_transaction(transaction, "commit")
|
|
176
|
+
except BaseException:
|
|
177
|
+
try:
|
|
178
|
+
await self._finish_graph_transaction(transaction, "rollback")
|
|
179
|
+
finally:
|
|
180
|
+
for action in reversed(self._graph_rollback_actions):
|
|
181
|
+
action()
|
|
182
|
+
raise
|
|
183
|
+
for action in self._graph_commit_actions:
|
|
184
|
+
action()
|
|
185
|
+
return result
|
|
186
|
+
finally:
|
|
187
|
+
self.insert_resource("dataService", provider)
|
|
188
|
+
self._graph_save_active = False
|
|
189
|
+
self._graph_commit_actions = []
|
|
190
|
+
self._graph_rollback_actions = []
|
|
191
|
+
self._resources.pop("fix_time", None)
|
|
192
|
+
self.finish_fix_evidence()
|
|
193
|
+
self._graph_save_owner.reset(owner_token)
|
|
194
|
+
|
|
195
|
+
async def _finish_graph_transaction(self, transaction, operation: str) -> None:
|
|
196
|
+
finish = getattr(transaction, operation)
|
|
197
|
+
try:
|
|
198
|
+
await finish(self)
|
|
199
|
+
except TypeError:
|
|
200
|
+
await finish()
|
|
201
|
+
|
|
202
|
+
def after_graph_commit(self, work) -> None:
|
|
203
|
+
if not self._graph_save_active:
|
|
204
|
+
raise RuntimeError("No graph save is active")
|
|
205
|
+
self._graph_commit_actions.append(work)
|
|
206
|
+
|
|
207
|
+
def after_graph_rollback(self, work) -> None:
|
|
208
|
+
if not self._graph_save_active:
|
|
209
|
+
raise RuntimeError("No graph save is active")
|
|
210
|
+
self._graph_rollback_actions.append(work)
|
|
211
|
+
|
|
106
212
|
async def get_in_store(self, key: str) -> Optional[Any]:
|
|
107
213
|
store = self.get_resource("data_store")
|
|
108
214
|
if store and hasattr(store, "get"):
|
|
@@ -454,12 +560,17 @@ class UserContext:
|
|
|
454
560
|
if not entity or record is None:
|
|
455
561
|
return
|
|
456
562
|
from datetime import datetime
|
|
457
|
-
self.
|
|
563
|
+
owns_fix_time = self.get_resource("fix_time") is None
|
|
564
|
+
if owns_fix_time:
|
|
565
|
+
self.insert_resource("fix_time", datetime.now())
|
|
566
|
+
self.begin_fix_evidence()
|
|
458
567
|
self.insert_resource("fix_operation", type(mutation).__name__.replace("Command", "").lower())
|
|
459
568
|
try:
|
|
460
569
|
self.check_and_fix_record(entity, record)
|
|
461
570
|
finally:
|
|
462
|
-
|
|
571
|
+
if owns_fix_time:
|
|
572
|
+
self._resources.pop("fix_time", None)
|
|
573
|
+
self.finish_fix_evidence()
|
|
463
574
|
self._resources.pop("fix_operation", None)
|
|
464
575
|
|
|
465
576
|
def translate_check_results(self, results: Any):
|
|
@@ -507,10 +618,17 @@ class UserContext:
|
|
|
507
618
|
def sql_log_options(self) -> 'SqlLogOptions':
|
|
508
619
|
opts = self.get_resource("sql_log_options")
|
|
509
620
|
if not opts:
|
|
510
|
-
opts = SqlLogOptions.
|
|
621
|
+
opts = SqlLogOptions.disabled()
|
|
511
622
|
self.insert_resource("sql_log_options", opts)
|
|
512
623
|
return opts
|
|
513
624
|
|
|
625
|
+
def with_diagnostic_sql_log_sink(self, sink: 'DiagnosticSqlLogSink') -> 'UserContext':
|
|
626
|
+
self.insert_resource("diagnostic_sql_log_sink", sink)
|
|
627
|
+
return self
|
|
628
|
+
|
|
629
|
+
def set_diagnostic_sql_log_sink(self, sink: Optional['DiagnosticSqlLogSink']):
|
|
630
|
+
self.insert_resource("diagnostic_sql_log_sink", sink)
|
|
631
|
+
|
|
514
632
|
def enable_select_sql_log(self):
|
|
515
633
|
self.set_sql_log_options(SqlLogOptions.select_only())
|
|
516
634
|
self.clear_sql_logs()
|
|
@@ -581,6 +699,9 @@ class UserContext:
|
|
|
581
699
|
logs = self.sql_logs()
|
|
582
700
|
logs.append(entry)
|
|
583
701
|
self._resources["sql_logs"] = logs
|
|
702
|
+
sink = self.get_resource("diagnostic_sql_log_sink")
|
|
703
|
+
if sink is not None:
|
|
704
|
+
sink.write(entry)
|
|
584
705
|
buf = self.get_resource("UnifiedLogBuffer")
|
|
585
706
|
if buf:
|
|
586
707
|
buf.entries.append(UnifiedLogEntry(
|
|
@@ -619,6 +740,9 @@ class UserContext:
|
|
|
619
740
|
logs = self.sql_logs()
|
|
620
741
|
logs.append(entry)
|
|
621
742
|
self._resources["sql_logs"] = logs
|
|
743
|
+
sink = self.get_resource("diagnostic_sql_log_sink")
|
|
744
|
+
if sink is not None:
|
|
745
|
+
sink.write(entry)
|
|
622
746
|
|
|
623
747
|
buf = self.get_resource("UnifiedLogBuffer")
|
|
624
748
|
if buf:
|
|
@@ -857,6 +981,22 @@ class SqlLogEntry:
|
|
|
857
981
|
affected_rows: Optional[int]
|
|
858
982
|
result_summary: str
|
|
859
983
|
|
|
984
|
+
class DiagnosticSqlLogSink:
|
|
985
|
+
"""Explicit value-bearing diagnostic SQL destination; never installed by default."""
|
|
986
|
+
def write(self, entry: SqlLogEntry) -> None:
|
|
987
|
+
raise NotImplementedError
|
|
988
|
+
|
|
989
|
+
class TextDiagnosticSqlLogSink(DiagnosticSqlLogSink):
|
|
990
|
+
def __init__(self, writer=print):
|
|
991
|
+
self._writer = writer
|
|
992
|
+
|
|
993
|
+
def write(self, entry: SqlLogEntry) -> None:
|
|
994
|
+
elapsed_us = int(entry.elapsed.total_seconds() * 1_000_000) if entry.elapsed else 0
|
|
995
|
+
self._writer(
|
|
996
|
+
f"[TeaQL SQL][{entry.operation.name.lower()}][{elapsed_us}us] "
|
|
997
|
+
f"{entry.result_summary}\n{entry.debug_sql}"
|
|
998
|
+
)
|
|
999
|
+
|
|
860
1000
|
@dataclass
|
|
861
1001
|
class InfoLogEntry:
|
|
862
1002
|
message: str
|
|
@@ -17,6 +17,9 @@ class ObjectLocation:
|
|
|
17
17
|
def index(self, value: int) -> ObjectLocation:
|
|
18
18
|
return ObjectLocation(self.segments + (("index", value),))
|
|
19
19
|
|
|
20
|
+
def prefixed_by(self, prefix: ObjectLocation) -> ObjectLocation:
|
|
21
|
+
return ObjectLocation(prefix.segments + self.segments)
|
|
22
|
+
|
|
20
23
|
@builtins.property
|
|
21
24
|
def model_path(self) -> str:
|
|
22
25
|
return self._render(lambda value: value)
|
|
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
|