teaql 0.2.2__tar.gz → 0.2.3__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.2 → teaql-0.2.3}/PKG-INFO +1 -1
  2. {teaql-0.2.2 → teaql-0.2.3}/pyproject.toml +1 -1
  3. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/mutation.py +2 -1
  4. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/data_service/__init__.py +2 -0
  5. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/context.py +30 -5
  6. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/sql/executor.py +55 -14
  7. {teaql-0.2.2 → teaql-0.2.3}/src/teaql.egg-info/PKG-INFO +1 -1
  8. {teaql-0.2.2 → teaql-0.2.3}/LICENSE +0 -0
  9. {teaql-0.2.2 → teaql-0.2.3}/README.md +0 -0
  10. {teaql-0.2.2 → teaql-0.2.3}/setup.cfg +0 -0
  11. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/__init__.py +0 -0
  12. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/__init__.py +0 -0
  13. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/entity.py +0 -0
  14. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/eval.py +0 -0
  15. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/expr.py +0 -0
  16. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/graph.py +0 -0
  17. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/list.py +0 -0
  18. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/meta.py +0 -0
  19. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/query.py +0 -0
  20. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/safe_expression.py +0 -0
  21. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/value.py +0 -0
  22. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/xls.py +0 -0
  23. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/__init__.py +0 -0
  24. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/mysql/__init__.py +0 -0
  25. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/mysql/dialect.py +0 -0
  26. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/mysql/transport.py +0 -0
  27. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/postgres/__init__.py +0 -0
  28. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/postgres/dialect.py +0 -0
  29. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/postgres/transport.py +0 -0
  30. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/redis/__init__.py +0 -0
  31. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/redis/remote.py +0 -0
  32. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/redis/store.py +0 -0
  33. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/sqlite/__init__.py +0 -0
  34. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/sqlite/dialect.py +0 -0
  35. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/sqlite/transport.py +0 -0
  36. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/tfp_client/__init__.py +0 -0
  37. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/py.typed +0 -0
  38. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/__init__.py +0 -0
  39. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/_schema_capability.py +0 -0
  40. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/audit.py +0 -0
  41. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/builtin-messages-v1.json +0 -0
  42. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/env.py +0 -0
  43. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/i18n.py +0 -0
  44. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/module.py +0 -0
  45. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/opentelemetry.py +0 -0
  46. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/store.py +0 -0
  47. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/telemetry.py +0 -0
  48. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/tools.py +0 -0
  49. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/sql/__init__.py +0 -0
  50. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/sql/dialect.py +0 -0
  51. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/sql/types.py +0 -0
  52. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/web_integration/__init__.py +0 -0
  53. {teaql-0.2.2 → teaql-0.2.3}/src/teaql/web_integration/fastapi/__init__.py +0 -0
  54. {teaql-0.2.2 → teaql-0.2.3}/src/teaql.egg-info/SOURCES.txt +0 -0
  55. {teaql-0.2.2 → teaql-0.2.3}/src/teaql.egg-info/dependency_links.txt +0 -0
  56. {teaql-0.2.2 → teaql-0.2.3}/src/teaql.egg-info/requires.txt +0 -0
  57. {teaql-0.2.2 → teaql-0.2.3}/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.2
3
+ Version: 0.2.3
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.2"
7
+ version = "0.2.3"
8
8
  description = "TeaQL Runtime and Data Service for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -8,6 +8,8 @@ class TraceNode:
8
8
  entity_type: str = ""
9
9
  entity_id: Optional[int] = None
10
10
  comment: str = ""
11
+ kind: str = "entity"
12
+ name: str = ""
11
13
 
12
14
  @dataclass
13
15
  class InsertCommand:
@@ -146,4 +148,3 @@ class MutationRequest:
146
148
  @classmethod
147
149
  def Batch(cls, cmds: List['MutationRequest']) -> 'MutationRequest':
148
150
  return cls(cmds)
149
-
@@ -67,6 +67,8 @@ class ExecutionMetadata:
67
67
  result_count: Optional[int] = None
68
68
  trace_chain: List[TraceNode] = field(default_factory=list)
69
69
  comment: Optional[str] = None
70
+ purpose: Optional[str] = None
71
+ audit_reason: Optional[str] = None
70
72
  backend_request_id: Optional[str] = None
71
73
  debug_query: Optional[str] = None
72
74
 
@@ -78,6 +78,8 @@ _default_id_set_store = InMemoryIdSetStore()
78
78
  class UserContext:
79
79
  def __init__(self):
80
80
  self._resources: Dict[str, Any] = {}
81
+ self._resources["sql_log_options"] = SqlLogOptions.all()
82
+ self._resources["diagnostic_sql_log_sink"] = TextDiagnosticSqlLogSink()
81
83
  self._metadata: Optional[Any] = None
82
84
  self._user_identifier: str = ""
83
85
  self._entities: List[Any] = []
@@ -618,7 +620,7 @@ class UserContext:
618
620
  def sql_log_options(self) -> 'SqlLogOptions':
619
621
  opts = self.get_resource("sql_log_options")
620
622
  if not opts:
621
- opts = SqlLogOptions.disabled()
623
+ opts = SqlLogOptions.all()
622
624
  self.insert_resource("sql_log_options", opts)
623
625
  return opts
624
626
 
@@ -645,6 +647,14 @@ class UserContext:
645
647
  self.set_sql_log_options(SqlLogOptions.disabled())
646
648
  self.clear_sql_logs()
647
649
 
650
+ def disable_select_sql_log(self):
651
+ options = self.sql_log_options()
652
+ self.set_sql_log_options(SqlLogOptions(False, options.mutation))
653
+
654
+ def disable_mutation_sql_log(self):
655
+ options = self.sql_log_options()
656
+ self.set_sql_log_options(SqlLogOptions(options.select, False))
657
+
648
658
  def sql_logs(self) -> List['SqlLogEntry']:
649
659
  return self._resources.get("sql_logs", [])
650
660
 
@@ -679,6 +689,10 @@ class UserContext:
679
689
  ended_at = getattr(metadata, 'ended_at', started_at)
680
690
  entry = SqlLogEntry(
681
691
  operation=op,
692
+ comment=getattr(metadata, 'comment', None),
693
+ purpose=getattr(metadata, 'purpose', None),
694
+ audit_reason=getattr(metadata, 'audit_reason', None),
695
+ trace_path=list(getattr(metadata, 'trace_chain', [])),
682
696
  sql=getattr(metadata, 'parameterized_sql', ''),
683
697
  params=list(getattr(metadata, 'parameters', [])),
684
698
  debug_sql=getattr(metadata, 'debug_query', '') or '',
@@ -719,6 +733,10 @@ class UserContext:
719
733
 
720
734
  entry = SqlLogEntry(
721
735
  operation=operation,
736
+ comment=None,
737
+ purpose=None,
738
+ audit_reason=None,
739
+ trace_path=[],
722
740
  sql=getattr(query, 'sql', ""),
723
741
  params=getattr(query, 'params', []),
724
742
  debug_sql=debug_sql,
@@ -944,8 +962,8 @@ class SqlLogOperation(Enum):
944
962
 
945
963
  @dataclass
946
964
  class SqlLogOptions:
947
- select: bool = False
948
- mutation: bool = False
965
+ select: bool = True
966
+ mutation: bool = True
949
967
 
950
968
  @classmethod
951
969
  def disabled(cls) -> 'SqlLogOptions':
@@ -969,6 +987,10 @@ class SqlLogOptions:
969
987
  @dataclass
970
988
  class SqlLogEntry:
971
989
  operation: SqlLogOperation
990
+ comment: Optional[str]
991
+ purpose: Optional[str]
992
+ audit_reason: Optional[str]
993
+ trace_path: List[Any]
972
994
  sql: str
973
995
  params: List[Any]
974
996
  debug_sql: str
@@ -982,7 +1004,7 @@ class SqlLogEntry:
982
1004
  result_summary: str
983
1005
 
984
1006
  class DiagnosticSqlLogSink:
985
- """Explicit value-bearing diagnostic SQL destination; never installed by default."""
1007
+ """Value-bearing diagnostic SQL destination; the text sink is installed by default."""
986
1008
  def write(self, entry: SqlLogEntry) -> None:
987
1009
  raise NotImplementedError
988
1010
 
@@ -994,7 +1016,10 @@ class TextDiagnosticSqlLogSink(DiagnosticSqlLogSink):
994
1016
  elapsed_us = int(entry.elapsed.total_seconds() * 1_000_000) if entry.elapsed else 0
995
1017
  self._writer(
996
1018
  f"[TeaQL SQL][{entry.operation.name.lower()}][{elapsed_us}us] "
997
- f"{entry.result_summary}\n{entry.debug_sql}"
1019
+ f"{entry.result_summary} comment={entry.comment!r} purpose={entry.purpose!r} "
1020
+ f"auditReason={entry.audit_reason!r} tracePath={entry.trace_path!r}\n"
1021
+ f"Parameterized SQL: {entry.sql} params={entry.params!r}\n"
1022
+ f"Debug SQL: {entry.debug_sql}"
998
1023
  )
999
1024
 
1000
1025
  @dataclass
@@ -16,7 +16,7 @@ from teaql.data_service import (
16
16
  DataServiceOperation, StreamChunk
17
17
  )
18
18
  from teaql.core.mutation import (
19
- InsertCommand, UpdateCommand, DeleteCommand, RecoverCommand
19
+ InsertCommand, UpdateCommand, DeleteCommand, RecoverCommand, TraceNode
20
20
  )
21
21
  from .types import CompiledQuery, SqlCompileError
22
22
  from .dialect import SqlDialect
@@ -187,10 +187,19 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
187
187
  execution_query, retained_order, retained_empty = await self._prepare_id_set_page(context, request.query)
188
188
  if retained_empty:
189
189
  now = datetime.now()
190
+ provider = str(self.dialect.kind()).lower()
190
191
  metadata = ExecutionMetadata(
191
- backend="sql", operation=DataServiceOperation.Query,
192
+ backend=provider, operation=DataServiceOperation.Query,
192
193
  started_at=now, ended_at=now, result_count=0,
193
- trace_chain=request.trace_chain, comment=request._comment,
194
+ trace_chain=[
195
+ TraceNode(kind="operation", name="query", comment="query"),
196
+ TraceNode(kind="request", name=request.query.entity,
197
+ comment=request.query.entity),
198
+ *request.trace_chain,
199
+ TraceNode(kind="provider", name=provider, comment=provider),
200
+ TraceNode(kind="sql", name="select", comment="select"),
201
+ ],
202
+ comment=request._comment, purpose=request._purpose,
194
203
  )
195
204
  if context is not None:
196
205
  context.record_metadata_log(metadata)
@@ -227,8 +236,8 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
227
236
  except Exception as e:
228
237
  raise TransportError(e)
229
238
 
230
- await self._enhance_relations(context, rows, request.query)
231
- await self._enhance_relation_aggregates(context, rows, request.query)
239
+ await self._enhance_relations(context, rows, request)
240
+ await self._enhance_relation_aggregates(context, rows, request)
232
241
  if retained_order:
233
242
  by_id = {int(row["id"]): row for row in rows if row.get("id") is not None}
234
243
  rows = [by_id[entity_id] for entity_id in retained_order if entity_id in by_id]
@@ -274,16 +283,25 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
274
283
 
275
284
  end = datetime.now()
276
285
 
286
+ provider = str(self.dialect.kind()).lower()
287
+ trace_path = [
288
+ TraceNode(kind="operation", name="query", comment="query"),
289
+ TraceNode(kind="request", name=request.query.entity, comment=request.query.entity),
290
+ *request.trace_chain,
291
+ TraceNode(kind="provider", name=provider, comment=provider),
292
+ TraceNode(kind="sql", name="select", comment="select"),
293
+ ]
277
294
  metadata = ExecutionMetadata(
278
- backend="sql",
295
+ backend=provider,
279
296
  operation=DataServiceOperation.Query,
280
297
  started_at=start,
281
298
  ended_at=end,
282
299
  parameterized_sql=compiled.sql_with_comment(),
283
300
  parameters=list(compiled.params),
284
301
  result_count=len(rows),
285
- trace_chain=request.trace_chain,
302
+ trace_chain=trace_path,
286
303
  comment=request._comment,
304
+ purpose=request._purpose,
287
305
  debug_query=compiled.debug_sql(self.dialect.kind())
288
306
  )
289
307
  if context is not None:
@@ -393,7 +411,8 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
393
411
  _canonical_id_set_value(normalized))
394
412
  return "teaql:id-set:v1:" + hashlib.sha256(repr(scope).encode("utf-8")).hexdigest()
395
413
 
396
- async def _enhance_relations(self, context, parents: List[Dict[str, Any]], query: SelectQuery) -> None:
414
+ async def _enhance_relations(self, context, parents: List[Dict[str, Any]], request: QueryRequest) -> None:
415
+ query = request.query
397
416
  if not parents or not query.relations:
398
417
  return
399
418
  parent_desc = self.schema_provider.get_entity(query.entity)
@@ -433,7 +452,11 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
433
452
  probe.and_filter(BinaryExpr(
434
453
  ColumnExpr(relation.foreign_key), BinaryOp.Eq,
435
454
  ValueExpr(Value.from_any(parent_id))))
436
- children.extend((await self.query(context, QueryRequest(probe))).rows)
455
+ child_trace = [*request.trace_chain, TraceNode(
456
+ kind="relation", name=f"{query.entity}.{load.name}",
457
+ comment=load.name)]
458
+ children.extend((await self.query(context, QueryRequest(
459
+ probe, child_trace, request._comment, request._purpose))).rows)
437
460
  selected_plan = "bounded_probes"
438
461
  probe_count = len(parent_ids)
439
462
  else:
@@ -442,7 +465,11 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
442
465
  ColumnExpr(relation.foreign_key), BinaryOp.In, ValueExpr(values)))
443
466
  if limited:
444
467
  child_query.partition_by_field(relation.foreign_key)
445
- children = (await self.query(context, QueryRequest(child_query))).rows
468
+ child_trace = [*request.trace_chain, TraceNode(
469
+ kind="relation", name=f"{query.entity}.{load.name}",
470
+ comment=load.name)]
471
+ children = (await self.query(context, QueryRequest(
472
+ child_query, child_trace, request._comment, request._purpose))).rows
446
473
  selected_plan = "window" if limited else "batch"
447
474
  probe_count = 0
448
475
  for child in children:
@@ -467,7 +494,8 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
467
494
  relation_scope.failure(error)
468
495
  raise
469
496
 
470
- async def _enhance_relation_aggregates(self, context, parents: List[Dict[str, Any]], query: SelectQuery) -> None:
497
+ async def _enhance_relation_aggregates(self, context, parents: List[Dict[str, Any]], request: QueryRequest) -> None:
498
+ query = request.query
471
499
  if not parents or not query.relation_aggregates:
472
500
  return
473
501
  parent_desc = self.schema_provider.get_entity(query.entity)
@@ -498,7 +526,11 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
498
526
  values = Value.List([Value.from_any(value) for value in parent_ids])
499
527
  child_query.and_filter(BinaryExpr(
500
528
  ColumnExpr(relation.foreign_key), BinaryOp.In, ValueExpr(values)))
501
- rows = (await self.query(context, QueryRequest(child_query))).rows
529
+ child_trace = [*request.trace_chain, TraceNode(
530
+ kind="relation", name=f"{query.entity}.{aggregate.relation_name}",
531
+ comment=aggregate.relation_name)]
532
+ rows = (await self.query(context, QueryRequest(
533
+ child_query, child_trace, request._comment, request._purpose))).rows
502
534
  child_desc = self.schema_provider.get_entity(relation.target_entity)
503
535
  foreign_property = child_desc.property_by_name(relation.foreign_key) if child_desc else None
504
536
  if foreign_property and foreign_property.column_name_val != relation.foreign_key:
@@ -668,8 +700,16 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
668
700
  f"authoritative persisted row not found for {req_data.entity}"))
669
701
  persisted_record = persisted_rows[0]
670
702
 
703
+ provider = str(self.dialect.kind()).lower()
704
+ trace_path = [
705
+ TraceNode(kind="operation", name="mutation", comment="mutation"),
706
+ TraceNode(kind="entity", name=req_data.entity, comment=req_data.entity),
707
+ *request.trace_chain(),
708
+ TraceNode(kind="provider", name=provider, comment=provider),
709
+ TraceNode(kind="sql", name=op, comment=op),
710
+ ]
671
711
  metadata = ExecutionMetadata(
672
- backend="sql",
712
+ backend=provider,
673
713
  operation={
674
714
  "insert": DataServiceOperation.Insert,
675
715
  "update": DataServiceOperation.Update,
@@ -681,8 +721,9 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
681
721
  parameterized_sql=compiled.sql_with_comment(),
682
722
  parameters=list(compiled.params),
683
723
  affected_rows=affected_rows,
684
- trace_chain=request.trace_chain(),
724
+ trace_chain=trace_path,
685
725
  comment=request.comment(),
726
+ audit_reason=request.comment(),
686
727
  debug_query=compiled.debug_sql(self.dialect.kind())
687
728
  )
688
729
  if context is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teaql
3
- Version: 0.2.2
3
+ Version: 0.2.3
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