warpzone-sdk 15.1.0.dev1__tar.gz → 15.1.1.dev1__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 (58) hide show
  1. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/PKG-INFO +1 -1
  2. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/pyproject.toml +1 -1
  3. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/deltastorage/table.py +4 -2
  4. warpzone_sdk-15.1.1.dev1/warpzone/monitor/traces.py +228 -0
  5. warpzone_sdk-15.1.0.dev1/warpzone/monitor/traces.py +0 -128
  6. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/README.md +0 -0
  7. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/__init__.py +0 -0
  8. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/blobstorage/__init__.py +0 -0
  9. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/blobstorage/client.py +0 -0
  10. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/db/__init__.py +0 -0
  11. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/db/client.py +0 -0
  12. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/deltastorage/__init__.py +0 -0
  13. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/deltastorage/data_types.py +0 -0
  14. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/deltastorage/generated_columns.py +0 -0
  15. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/deltastorage/lock_client.py +0 -0
  16. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/deltastorage/schema.py +0 -0
  17. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/deltastorage/slicing.py +0 -0
  18. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/deltastorage/store.py +0 -0
  19. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/enums/__init__.py +0 -0
  20. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/enums/topicenum.py +0 -0
  21. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/__init__.py +0 -0
  22. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/checks.py +0 -0
  23. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/functionize.py +0 -0
  24. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/integrations.py +0 -0
  25. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/monitor.py +0 -0
  26. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/process.py +0 -0
  27. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/processors/__init__.py +0 -0
  28. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/processors/dependencies.py +0 -0
  29. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/processors/outputs.py +0 -0
  30. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/processors/triggers.py +0 -0
  31. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/signature.py +0 -0
  32. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/function/types.py +0 -0
  33. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/healthchecks/__init__.py +0 -0
  34. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/healthchecks/model.py +0 -0
  35. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/monitor/__init__.py +0 -0
  36. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/monitor/logs.py +0 -0
  37. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/servicebus/data/__init__.py +0 -0
  38. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/servicebus/data/client.py +0 -0
  39. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/servicebus/events/__init__.py +0 -0
  40. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/servicebus/events/client.py +0 -0
  41. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/servicebus/events/triggers.py +0 -0
  42. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tablestorage/db/__init__.py +0 -0
  43. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tablestorage/db/base_client.py +0 -0
  44. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tablestorage/db/client.py +0 -0
  45. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tablestorage/db/table_config.py +0 -0
  46. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tablestorage/tables/__init__.py +0 -0
  47. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tablestorage/tables/client.py +0 -0
  48. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tablestorage/tables/entities.py +0 -0
  49. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tablestorage/tables/helpers.py +0 -0
  50. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/testing/__init__.py +0 -0
  51. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/testing/assertions.py +0 -0
  52. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/testing/data.py +0 -0
  53. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/testing/matchers.py +0 -0
  54. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tools/__init__.py +0 -0
  55. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/tools/copy.py +0 -0
  56. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/transform/__init__.py +0 -0
  57. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/transform/data.py +0 -0
  58. {warpzone_sdk-15.1.0.dev1 → warpzone_sdk-15.1.1.dev1}/warpzone/transform/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warpzone-sdk
3
- Version: 15.1.0.dev1
3
+ Version: 15.1.1.dev1
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.1.0.dev1"
3
+ version = "15.1.1.dev1"
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"
@@ -53,8 +53,10 @@ class Table:
53
53
  As the `Table`-class is lazily initialized,
54
54
  the `delta_table`-property is initialized on the first access
55
55
  and saved for future use to minimize overhead.
56
- It is *important* that this property is not initialized when creating
57
- the `Table`-object, because using the same instance can lead to transaction
56
+ It is *important* that this property is only initialized within
57
+ a lock when doing concurrent reads/writes
58
+ and not initialized when creating the `Table`-object.
59
+ This is important because using the same instance can lead to transaction
58
60
  issues in delta as DeltaTable uses metadata (transaction id) from
59
61
  the first time the object is instantiated.
60
62
  """
@@ -0,0 +1,228 @@
1
+ import inspect
2
+ import logging
3
+ import os
4
+ import threading
5
+ from contextlib import contextmanager
6
+ from functools import wraps
7
+ from logging import StreamHandler
8
+ from typing import Callable
9
+
10
+ from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter
11
+ from opentelemetry import context, trace
12
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
13
+ from opentelemetry.sdk.trace import Tracer, TracerProvider
14
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
15
+ from opentelemetry.sdk.trace.sampling import ALWAYS_ON
16
+ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
17
+
18
+ logger = logging.getLogger(__name__)
19
+ logger.addHandler(StreamHandler())
20
+
21
+
22
+ class WarpzoneTracer:
23
+ """Wrapper around OpenTelemetry tracer with additional trace decorator method"""
24
+
25
+ def __init__(self, otel_tracer: Tracer):
26
+ # Store the original tracer instead of calling super().__init__
27
+ # because we want to wrap an existing tracer, not create a new one
28
+ self._tracer = otel_tracer
29
+
30
+ def __getattr__(self, name):
31
+ """Delegate all attributes to the underlying tracer"""
32
+ return getattr(self._tracer, name)
33
+
34
+ def trace_function(
35
+ self,
36
+ name: str = None,
37
+ set_args_as_attributes: bool = False,
38
+ on_input: Callable = None,
39
+ on_output: Callable = None,
40
+ ):
41
+ """
42
+ Decorator to trace a function using this tracer object.
43
+
44
+ This decorator wraps functions with OpenTelemetry tracing, allowing for:
45
+ - Automatically create spans for function execution
46
+ - Customize the span name
47
+ - Add custom attributes to the span
48
+ - Add custom logic for inputs and outputs
49
+
50
+ Args:
51
+ name: Optional name for the span. If not provided, uses the function name.
52
+ set_args_as_attributes: If True, sets function arguments as attributes.
53
+ on_input: Optional callback called with (span, *args, **kwargs) before.
54
+ on_output: Optional callback called with (span, result) after.
55
+
56
+ Example:
57
+ # Simple tracing with function name
58
+ tracer = get_tracer(__name__)
59
+
60
+ @tracer.trace_function()
61
+ def my_function():
62
+ pass
63
+
64
+ # Custom tracing with input/output callbacks
65
+ @tracer.trace_function(
66
+ on_input=lambda span, new_data, existing_data, now: (
67
+ span.set_attribute("new_records", len(new_data)),
68
+ span.set_attribute("existing_records", len(existing_data)),
69
+ ),
70
+ on_output=lambda span, result: (
71
+ span.set_attribute("merged_records", len(result))
72
+ ),
73
+ )
74
+ def merge_new_and_existing(new_data, existing_data, now):
75
+ pass
76
+ """
77
+
78
+ def decorator(func: Callable) -> Callable:
79
+ @wraps(func)
80
+ def wrapper(*args, **kwargs):
81
+ span_name = name or func.__name__
82
+ with self._tracer.start_as_current_span(span_name) as span:
83
+ if set_args_as_attributes:
84
+ # Get parameter names from function signature
85
+ sig = inspect.signature(func)
86
+ param_names = list(sig.parameters.keys())
87
+
88
+ # Set positional arguments with their parameter names
89
+ for i, arg in enumerate(args):
90
+ if i < len(param_names):
91
+ span.set_attribute(param_names[i], str(arg))
92
+ else:
93
+ # Fallback for *args if there are more args than params
94
+ span.set_attribute(f"arg_{i}", str(arg))
95
+
96
+ # Set keyword arguments
97
+ for key, value in kwargs.items():
98
+ span.set_attribute(str(key), str(value))
99
+
100
+ # Call on_input callback if provided
101
+ if on_input:
102
+ on_input(span, *args, **kwargs)
103
+
104
+ result = func(*args, **kwargs)
105
+
106
+ # Call on_output callback if provided
107
+ if on_output:
108
+ on_output(span, result)
109
+
110
+ return result
111
+
112
+ return wrapper
113
+
114
+ return decorator
115
+
116
+
117
+ tracer = WarpzoneTracer(trace.get_tracer(__name__))
118
+
119
+
120
+ _TRACING_LOCK = threading.Lock()
121
+ TRACING_IS_CONFIGURED = False
122
+
123
+
124
+ def configure_tracing():
125
+ global TRACING_IS_CONFIGURED
126
+ # Add thread locking to avoid race conditions during setup
127
+ with _TRACING_LOCK:
128
+ if TRACING_IS_CONFIGURED:
129
+ # tracing should only be set up once
130
+ # to avoid duplicated trace handling.
131
+ # Global variables is the pattern used
132
+ # by opentelemetry, so we use the same
133
+ return
134
+
135
+ # set up tracer provider based on the Azure Function resource
136
+ # (this is make sure App Insights can track the trace source correctly)
137
+ # (https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-enable?tabs=net#set-the-cloud-role-name-and-the-cloud-role-instance).
138
+ # We use the ALWAYS ON sampler since otherwise spans will not be
139
+ # recording upon creation
140
+ # (https://anecdotes.dev/opentelemetry-on-google-cloud-unraveling-the-mystery-f61f044c18be)
141
+ service_name = os.getenv("WEBSITE_SITE_NAME") or "unknown-service"
142
+ resource = Resource.create({SERVICE_NAME: service_name})
143
+ trace.set_tracer_provider(
144
+ TracerProvider(
145
+ sampler=ALWAYS_ON,
146
+ resource=resource,
147
+ )
148
+ )
149
+
150
+ # setup azure monitor trace exporter to send telemetry to App Insights
151
+ try:
152
+ trace_exporter = AzureMonitorTraceExporter()
153
+ except ValueError:
154
+ logger.warning(
155
+ "Cant set up tracing to App Insights,"
156
+ " as no connection string is set."
157
+ )
158
+ else:
159
+ span_processor = BatchSpanProcessor(trace_exporter)
160
+ trace.get_tracer_provider().add_span_processor(span_processor)
161
+
162
+ TRACING_IS_CONFIGURED = True
163
+
164
+
165
+ @contextmanager
166
+ def set_trace_context(trace_parent: str, trace_state: str = ""):
167
+ """Context manager for setting the trace context
168
+
169
+ Args:
170
+ trace_parent (str): Trace parent ID
171
+ trace_state (str, optional): Trace state. Defaults to "".
172
+ """
173
+ carrier = {"traceparent": trace_parent, "tracestate": trace_state}
174
+ ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
175
+
176
+ token = context.attach(ctx) # attach context before run
177
+ try:
178
+ yield
179
+ finally:
180
+ context.detach(token) # detach context after run
181
+
182
+
183
+ def get_tracer(name: str):
184
+ otel_tracer = trace.get_tracer(name)
185
+ return WarpzoneTracer(otel_tracer)
186
+
187
+
188
+ def get_current_diagnostic_id() -> str:
189
+ """Gets diagnostic id from current span
190
+
191
+ The diagnostic id is a concatenation of operation-id and parent-id
192
+
193
+ Returns:
194
+ str: diagnostic id
195
+ """
196
+ span = trace.get_current_span()
197
+
198
+ if not span.is_recording():
199
+ return ""
200
+
201
+ operation_id = "{:016x}".format(span.context.trace_id)
202
+ parent_id = "{:016x}".format(span.context.span_id)
203
+
204
+ diagnostic_id = f"00-{operation_id}-{parent_id}-01"
205
+
206
+ return diagnostic_id
207
+
208
+
209
+ # Service Bus trace constants (these were removed from azure-servicebus SDK)
210
+ _SB_TRACE_NAMESPACE = "Microsoft.ServiceBus"
211
+
212
+
213
+ @contextmanager
214
+ def servicebus_send_span(subject: str):
215
+ """Start span for Service Bus message tracing.
216
+
217
+ Args:
218
+ subject: The message subject (used as span name for easy identification)
219
+
220
+ Yields:
221
+ Span: the span
222
+ """
223
+ with tracer.start_as_current_span(
224
+ subject, kind=trace.SpanKind.PRODUCER
225
+ ) as msg_span:
226
+ msg_span.set_attributes({"az.namespace": _SB_TRACE_NAMESPACE})
227
+
228
+ yield msg_span
@@ -1,128 +0,0 @@
1
- import logging
2
- import os
3
- import threading
4
- from contextlib import contextmanager
5
- from logging import StreamHandler
6
-
7
- from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter
8
- from opentelemetry import context, trace
9
- from opentelemetry.sdk.resources import SERVICE_NAME, Resource
10
- from opentelemetry.sdk.trace import TracerProvider
11
- from opentelemetry.sdk.trace.export import BatchSpanProcessor
12
- from opentelemetry.sdk.trace.sampling import ALWAYS_ON
13
- from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
14
-
15
- logger = logging.getLogger(__name__)
16
- logger.addHandler(StreamHandler())
17
-
18
- tracer = trace.get_tracer(__name__)
19
-
20
- _TRACING_LOCK = threading.Lock()
21
- TRACING_IS_CONFIGURED = False
22
-
23
-
24
- def configure_tracing():
25
- global TRACING_IS_CONFIGURED
26
- # Add thread locking to avoid race conditions during setup
27
- with _TRACING_LOCK:
28
- if TRACING_IS_CONFIGURED:
29
- # tracing should only be set up once
30
- # to avoid duplicated trace handling.
31
- # Global variables is the pattern used
32
- # by opentelemetry, so we use the same
33
- return
34
-
35
- # set up tracer provider based on the Azure Function resource
36
- # (this is make sure App Insights can track the trace source correctly)
37
- # (https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-enable?tabs=net#set-the-cloud-role-name-and-the-cloud-role-instance).
38
- # We use the ALWAYS ON sampler since otherwise spans will not be
39
- # recording upon creation
40
- # (https://anecdotes.dev/opentelemetry-on-google-cloud-unraveling-the-mystery-f61f044c18be)
41
- service_name = os.getenv("WEBSITE_SITE_NAME") or "unknown-service"
42
- resource = Resource.create({SERVICE_NAME: service_name})
43
- trace.set_tracer_provider(
44
- TracerProvider(
45
- sampler=ALWAYS_ON,
46
- resource=resource,
47
- )
48
- )
49
-
50
- # setup azure monitor trace exporter to send telemetry to App Insights
51
- try:
52
- trace_exporter = AzureMonitorTraceExporter()
53
- except ValueError:
54
- logger.warning(
55
- "Cant set up tracing to App Insights,"
56
- " as no connection string is set."
57
- )
58
- else:
59
- span_processor = BatchSpanProcessor(trace_exporter)
60
- trace.get_tracer_provider().add_span_processor(span_processor)
61
-
62
- TRACING_IS_CONFIGURED = True
63
-
64
-
65
- @contextmanager
66
- def set_trace_context(trace_parent: str, trace_state: str = ""):
67
- """Context manager for setting the trace context
68
-
69
- Args:
70
- trace_parent (str): Trace parent ID
71
- trace_state (str, optional): Trace state. Defaults to "".
72
- """
73
- carrier = {"traceparent": trace_parent, "tracestate": trace_state}
74
- ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
75
-
76
- token = context.attach(ctx) # attach context before run
77
- try:
78
- yield
79
- finally:
80
- context.detach(token) # detach context after run
81
-
82
-
83
- def get_tracer(name: str):
84
- tracer = trace.get_tracer(name)
85
- return tracer
86
-
87
-
88
- def get_current_diagnostic_id() -> str:
89
- """Gets diagnostic id from current span
90
-
91
- The diagnostic id is a concatenation of operation-id and parent-id
92
-
93
- Returns:
94
- str: diagnostic id
95
- """
96
- span = trace.get_current_span()
97
-
98
- if not span.is_recording():
99
- return ""
100
-
101
- operation_id = "{:016x}".format(span.context.trace_id)
102
- parent_id = "{:016x}".format(span.context.span_id)
103
-
104
- diagnostic_id = f"00-{operation_id}-{parent_id}-01"
105
-
106
- return diagnostic_id
107
-
108
-
109
- # Service Bus trace constants (these were removed from azure-servicebus SDK)
110
- _SB_TRACE_NAMESPACE = "Microsoft.ServiceBus"
111
-
112
-
113
- @contextmanager
114
- def servicebus_send_span(subject: str) -> trace.Span:
115
- """Start span for Service Bus message tracing.
116
-
117
- Args:
118
- subject: The message subject (used as span name for easy identification)
119
-
120
- Yields:
121
- trace.Span: the span
122
- """
123
- with tracer.start_as_current_span(
124
- subject, kind=trace.SpanKind.PRODUCER
125
- ) as msg_span:
126
- msg_span.set_attributes({"az.namespace": _SB_TRACE_NAMESPACE})
127
-
128
- yield msg_span