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.
Files changed (57) hide show
  1. {teaql-0.2.0 → teaql-0.2.2}/PKG-INFO +1 -1
  2. {teaql-0.2.0 → teaql-0.2.2}/pyproject.toml +1 -1
  3. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/__init__.py +5 -2
  4. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/context.py +143 -3
  5. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/i18n.py +3 -0
  6. {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/PKG-INFO +1 -1
  7. {teaql-0.2.0 → teaql-0.2.2}/LICENSE +0 -0
  8. {teaql-0.2.0 → teaql-0.2.2}/README.md +0 -0
  9. {teaql-0.2.0 → teaql-0.2.2}/setup.cfg +0 -0
  10. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/__init__.py +0 -0
  11. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/__init__.py +0 -0
  12. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/entity.py +0 -0
  13. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/eval.py +0 -0
  14. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/expr.py +0 -0
  15. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/graph.py +0 -0
  16. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/list.py +0 -0
  17. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/meta.py +0 -0
  18. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/mutation.py +0 -0
  19. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/query.py +0 -0
  20. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/safe_expression.py +0 -0
  21. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/value.py +0 -0
  22. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/core/xls.py +0 -0
  23. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/data_service/__init__.py +0 -0
  24. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/__init__.py +0 -0
  25. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/mysql/__init__.py +0 -0
  26. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/mysql/dialect.py +0 -0
  27. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/mysql/transport.py +0 -0
  28. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/postgres/__init__.py +0 -0
  29. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/postgres/dialect.py +0 -0
  30. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/postgres/transport.py +0 -0
  31. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/redis/__init__.py +0 -0
  32. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/redis/remote.py +0 -0
  33. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/redis/store.py +0 -0
  34. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/sqlite/__init__.py +0 -0
  35. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/sqlite/dialect.py +0 -0
  36. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/sqlite/transport.py +0 -0
  37. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/provider/tfp_client/__init__.py +0 -0
  38. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/py.typed +0 -0
  39. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/_schema_capability.py +0 -0
  40. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/audit.py +0 -0
  41. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/builtin-messages-v1.json +0 -0
  42. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/env.py +0 -0
  43. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/module.py +0 -0
  44. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/opentelemetry.py +0 -0
  45. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/store.py +0 -0
  46. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/telemetry.py +0 -0
  47. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/runtime/tools.py +0 -0
  48. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/sql/__init__.py +0 -0
  49. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/sql/dialect.py +0 -0
  50. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/sql/executor.py +0 -0
  51. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/sql/types.py +0 -0
  52. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/web_integration/__init__.py +0 -0
  53. {teaql-0.2.0 → teaql-0.2.2}/src/teaql/web_integration/fastapi/__init__.py +0 -0
  54. {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/SOURCES.txt +0 -0
  55. {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/dependency_links.txt +0 -0
  56. {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/requires.txt +0 -0
  57. {teaql-0.2.0 → teaql-0.2.2}/src/teaql.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teaql
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: TeaQL Runtime and Data Service for Python
5
5
  Author: TeaQL
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "teaql"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "TeaQL Runtime and Data Service for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,4 +1,7 @@
1
- from .context import ContextEntityRef, ContextRootError, UserContext, TeaqlRuntime, SqlLogEntry, SqlLogOperation
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.insert_resource("fix_time", datetime.now())
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
- self._resources.pop("fix_time", None)
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.all()
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teaql
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: TeaQL Runtime and Data Service for Python
5
5
  Author: TeaQL
6
6
  License-Expression: Apache-2.0
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