warpzone-sdk 15.0.0.dev3__tar.gz → 15.0.0.dev5__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. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/PKG-INFO +1 -1
  2. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/pyproject.toml +1 -1
  3. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/monitor.py +14 -28
  4. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/monitor/logs.py +12 -2
  5. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/monitor/traces.py +24 -11
  6. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/README.md +0 -0
  7. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/__init__.py +0 -0
  8. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/blobstorage/__init__.py +0 -0
  9. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/blobstorage/client.py +0 -0
  10. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/db/__init__.py +0 -0
  11. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/db/client.py +0 -0
  12. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/deltastorage/__init__.py +0 -0
  13. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/deltastorage/data_types.py +0 -0
  14. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/deltastorage/generated_columns.py +0 -0
  15. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/deltastorage/lock_client.py +0 -0
  16. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/deltastorage/schema.py +0 -0
  17. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/deltastorage/slicing.py +0 -0
  18. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/deltastorage/store.py +0 -0
  19. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/deltastorage/table.py +0 -0
  20. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/enums/__init__.py +0 -0
  21. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/enums/topicenum.py +0 -0
  22. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/__init__.py +0 -0
  23. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/checks.py +0 -0
  24. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/functionize.py +0 -0
  25. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/integrations.py +0 -0
  26. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/process.py +0 -0
  27. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/processors/__init__.py +0 -0
  28. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/processors/dependencies.py +0 -0
  29. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/processors/outputs.py +0 -0
  30. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/processors/triggers.py +0 -0
  31. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/signature.py +0 -0
  32. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/function/types.py +0 -0
  33. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/healthchecks/__init__.py +0 -0
  34. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/healthchecks/model.py +0 -0
  35. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/monitor/__init__.py +0 -0
  36. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/servicebus/data/__init__.py +0 -0
  37. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/servicebus/data/client.py +0 -0
  38. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/servicebus/events/__init__.py +0 -0
  39. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/servicebus/events/client.py +0 -0
  40. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/servicebus/events/triggers.py +0 -0
  41. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tablestorage/db/__init__.py +0 -0
  42. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tablestorage/db/base_client.py +0 -0
  43. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tablestorage/db/client.py +0 -0
  44. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tablestorage/db/table_config.py +0 -0
  45. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tablestorage/tables/__init__.py +0 -0
  46. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tablestorage/tables/client.py +0 -0
  47. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tablestorage/tables/entities.py +0 -0
  48. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tablestorage/tables/helpers.py +0 -0
  49. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/testing/__init__.py +0 -0
  50. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/testing/assertions.py +0 -0
  51. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/testing/data.py +0 -0
  52. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/testing/matchers.py +0 -0
  53. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tools/__init__.py +0 -0
  54. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/tools/copy.py +0 -0
  55. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/transform/__init__.py +0 -0
  56. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/transform/data.py +0 -0
  57. {warpzone_sdk-15.0.0.dev3 → warpzone_sdk-15.0.0.dev5}/warpzone/transform/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warpzone-sdk
3
- Version: 15.0.0.dev3
3
+ Version: 15.0.0.dev5
4
4
  Summary: The main objective of this package is to centralize logic used to interact with Azure Functions, Azure Service Bus and Azure Table Storage
5
5
  Author: Team Enigma
6
6
  Author-email: enigma@energinet.dk
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "warpzone-sdk"
3
- version = "15.0.0.dev3"
3
+ version = "15.0.0.dev5"
4
4
  description = "The main objective of this package is to centralize logic used to interact with Azure Functions, Azure Service Bus and Azure Table Storage"
5
5
  authors = [{ name = "Team Enigma", email = "enigma@energinet.dk" }]
6
6
  requires-python = ">=3.10"
@@ -7,41 +7,29 @@ from azure.monitor.opentelemetry import configure_azure_monitor
7
7
  from opentelemetry import trace
8
8
 
9
9
  from warpzone.function.types import SingleArgumentCallable
10
- from warpzone.monitor import logs, traces
10
+ from warpzone.monitor import traces
11
11
 
12
- # Lazy initialization flag to ensure configure_azure_monitor is called only once
13
- _monitor_configured = False
14
-
15
-
16
- def _ensure_monitor_configured():
17
- """Configure Azure Monitor lazily on first use to ensure proper trace context."""
18
- global _monitor_configured
19
- if not _monitor_configured:
20
- configure_azure_monitor()
21
-
22
- # Apply trace filtering to suppress all Azure SDK traces except Service Bus
23
- tracer_provider = trace.get_tracer_provider()
24
- if hasattr(tracer_provider, "_active_span_processor"):
25
- # Wrap the existing span processor with our filter
26
- original_processor = tracer_provider._active_span_processor
27
- filtered_processor = traces.AzureSDKTraceFilter(original_processor)
28
- tracer_provider._active_span_processor = filtered_processor
29
-
30
- _monitor_configured = True
12
+ # Configure Azure Monitor at import to ensure proper telemetry setup.
13
+ # The trace context is set per-invocation in run_in_trace_context to ensure
14
+ # proper operation ID correlation for each function execution.
15
+ configure_azure_monitor()
31
16
 
17
+ # Apply trace filtering to suppress all Azure SDK traces except Service Bus
18
+ tracer_provider = trace.get_tracer_provider()
19
+ if hasattr(tracer_provider, "_active_span_processor"):
20
+ # Wrap the existing span processor with our filter
21
+ original_processor = tracer_provider._active_span_processor
22
+ filtered_processor = traces.AzureSDKTraceFilter(original_processor)
23
+ tracer_provider._active_span_processor = filtered_processor
32
24
 
33
25
  SUBJECT_IDENTIFIER = "<Subject>"
34
26
 
35
- tracer = traces.get_tracer(__name__)
36
- logger = logs.get_logger(__name__)
37
-
38
27
 
39
28
  @contextmanager
40
29
  def run_in_trace_context(context: func.Context):
41
30
  trace_context = context.trace_context
42
- with traces.set_trace_context(
43
- trace_context.trace_parent, trace_context.trace_state
44
- ):
31
+ span_name = context.function_name or "azure_function"
32
+ with traces.set_trace_context(trace_context.trace_parent, span_name=span_name):
45
33
  yield
46
34
 
47
35
 
@@ -65,13 +53,11 @@ def monitor(main: SingleArgumentCallable) -> Callable:
65
53
  """
66
54
 
67
55
  async def wrapper_async(arg, context: func.Context):
68
- _ensure_monitor_configured()
69
56
  with run_in_trace_context(context):
70
57
  result = await main(arg)
71
58
  return result
72
59
 
73
60
  def wrapper(arg, context: func.Context):
74
- _ensure_monitor_configured()
75
61
  with run_in_trace_context(context):
76
62
  result = main(arg)
77
63
  return result
@@ -17,8 +17,18 @@ logging.getLogger("azure.monitor.opentelemetry.exporter").setLevel(logging.WARNI
17
17
 
18
18
 
19
19
  def get_logger(name: str):
20
- # set up standard logger
20
+ """Get a logger instance.
21
+
22
+ The logger will automatically use the current OpenTelemetry trace context
23
+ for correlation in Application Insights. The trace context is set by the
24
+ monitor decorator's run_in_trace_context context manager.
25
+
26
+ Args:
27
+ name: Logger name, typically __name__
28
+
29
+ Returns:
30
+ A configured logger instance
31
+ """
21
32
  logger = logging.getLogger(name)
22
33
  logger.setLevel(logging.INFO)
23
-
24
34
  return logger
@@ -31,8 +31,15 @@ class AzureSDKTraceFilter(SpanProcessor):
31
31
 
32
32
  def on_end(self, span: ReadableSpan) -> None:
33
33
  """Called when a span is ended. Filter based on span attributes."""
34
+ # Safely get span name, handling None or mock objects
35
+ span_name = getattr(span, "name", None)
36
+ if span_name is None or not isinstance(span_name, str):
37
+ # Pass through spans without valid names (e.g., in test mocks)
38
+ self.wrapped_processor.on_end(span)
39
+ return
40
+
34
41
  # Check if service bus span - always allow
35
- if "servicebus.message" in span.name.lower():
42
+ if "servicebus.message" in span_name.lower():
36
43
  self.wrapped_processor.on_end(span)
37
44
  return
38
45
 
@@ -56,21 +63,27 @@ class AzureSDKTraceFilter(SpanProcessor):
56
63
 
57
64
 
58
65
  @contextmanager
59
- def set_trace_context(trace_parent: str, trace_state: str = ""):
60
- """Context manager for setting the trace context
66
+ def set_trace_context(trace_parent: str, span_name: str = "function_execution"):
67
+ """Context manager for setting the trace context with a recording span.
68
+
69
+ Creates a child span of the propagated trace context. This is necessary
70
+ because the LoggingHandler requires a recording span to properly correlate
71
+ logs with the trace. A NonRecordingSpan (which is what TraceContextTextMapPropagator
72
+ creates) may not be correctly handled during log batching/export.
61
73
 
62
74
  Args:
63
- trace_parent (str): Trace parent ID
64
- trace_state (str, optional): Trace state. Defaults to "".
75
+ trace_parent (str): Trace parent ID from the incoming request
76
+ span_name (str): Name for the span. Defaults to "function_execution".
65
77
  """
66
- carrier = {"traceparent": trace_parent, "tracestate": trace_state}
78
+ carrier = {"traceparent": trace_parent}
67
79
  ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
68
80
 
69
- token = context.attach(ctx) # attach context before run
70
- try:
71
- yield
72
- finally:
73
- context.detach(token) # detach context after run
81
+ # Start a recording span as a child of the propagated context.
82
+ # This ensures logs are properly correlated with the trace.
83
+ tracer = trace.get_tracer(__name__)
84
+ with trace.use_span(trace.get_current_span(ctx), end_on_exit=False):
85
+ with tracer.start_as_current_span(span_name):
86
+ yield
74
87
 
75
88
 
76
89
  def get_tracer(name: str):