odigos-opentelemetry-python 1.0.77__tar.gz → 1.0.79__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 (34) hide show
  1. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/PKG-INFO +1 -1
  2. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/components.py +30 -7
  3. odigos_opentelemetry_python-1.0.79/initializer/odigos_sampler.py +168 -0
  4. odigos_opentelemetry_python-1.0.79/initializer/version.py +1 -0
  5. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/PKG-INFO +1 -1
  6. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/SOURCES.txt +0 -1
  7. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/http_client.py +1 -2
  8. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/pyproject.toml +1 -1
  9. odigos_opentelemetry_python-1.0.77/initializer/odigos_sampler.py +0 -98
  10. odigos_opentelemetry_python-1.0.77/initializer/sampling_operators.py +0 -7
  11. odigos_opentelemetry_python-1.0.77/initializer/version.py +0 -1
  12. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/LICENSE +0 -0
  13. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/README.md +0 -0
  14. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/__init__.py +0 -0
  15. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/distro/__init__.py +0 -0
  16. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/distro/distro.py +0 -0
  17. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/distro/instrumentation_registry.py +0 -0
  18. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/envs.py +0 -0
  19. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/exit_hook.py +0 -0
  20. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/lib_handling.py +0 -0
  21. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/post_instrument.py +0 -0
  22. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/process_resource.py +0 -0
  23. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/dependency_links.txt +0 -0
  24. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/entry_points.txt +0 -0
  25. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/requires.txt +0 -0
  26. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/top_level.txt +0 -0
  27. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/__init__.py +0 -0
  28. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/anyvalue_pb2.py +0 -0
  29. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/config.py +0 -0
  30. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/health_status.py +0 -0
  31. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/opamp_pb2.py +0 -0
  32. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/opamp_registry.py +0 -0
  33. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/utils.py +0 -0
  34. {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odigos-opentelemetry-python
3
- Version: 1.0.77
3
+ Version: 1.0.79
4
4
  Summary: Odigos Initializer for Python OpenTelemetry Components
5
5
  Author-email: odigos-io <support@odigos.io>
6
6
  Requires-Python: >=3.9
@@ -7,6 +7,19 @@ import atexit
7
7
  import sys
8
8
  import os
9
9
 
10
+
11
+ # opentelemetry is a namespace package — Python merges all opentelemetry/
12
+ # directories from sys.path into opentelemetry.__path__. If the user app
13
+ # bundles a different opentelemetry-api version, the user's copy can end up
14
+ # first and break agent imports (e.g. ImportError on std_to_otel).
15
+ # Fix: reorder opentelemetry.__path__ so agent paths are always searched first.
16
+ import opentelemetry
17
+
18
+ _agent_prefix = ('/var/odigos/', '/etc/odigos-vmagent/')
19
+ _agent_otel = [p for p in opentelemetry.__path__ if any(p.startswith(pfx) for pfx in _agent_prefix)]
20
+ _other_otel = [p for p in opentelemetry.__path__ if not any(p.startswith(pfx) for pfx in _agent_prefix)]
21
+ opentelemetry.__path__[:] = _agent_otel + _other_otel
22
+
10
23
  import opentelemetry.sdk._configuration as sdk_config
11
24
  from .process_resource import OdigosProcessResourceDetector
12
25
  from opentelemetry.sdk.resources import Resource
@@ -40,6 +53,8 @@ from opamp.http_client import OpAMPHTTPClient, MockOpAMPClient
40
53
 
41
54
  MINIMUM_PYTHON_SUPPORTED_VERSION = (3, 9)
42
55
 
56
+ _odigos_sampler = None
57
+
43
58
 
44
59
  def initialize_components(trace_exporters = False, span_processor = None):
45
60
  # Ensure each child process after fork gets a fresh OpAMP client
@@ -105,12 +120,14 @@ def initialize_components(trace_exporters = False, span_processor = None):
105
120
  reload_distro_modules()
106
121
 
107
122
  def initialize_traces_if_enabled(trace_exporters, resource, span_processor = None, signals = None, initial_sampler_config = None):
123
+ global _odigos_sampler
108
124
  # In case collectors signals support receiving traces, initialize the tracer provider with the exporters
109
125
  traces_enabled = signals.get("traceSignal", False)
110
126
 
111
127
  if traces_enabled:
112
128
 
113
129
  odigos_sampler = OdigosSampler()
130
+ _odigos_sampler = odigos_sampler
114
131
  sampler = ParentBased(odigos_sampler)
115
132
 
116
133
  # Apply initial sampler config if provided (from OpAMP first message)
@@ -237,10 +254,8 @@ def update_agent_config(conf: Config):
237
254
  if 'traces' in conf.sample_config and 'headSampling' in conf.sample_config['traces']:
238
255
  head_sampling_config = conf.sample_config['traces']['headSampling']
239
256
 
240
- # Determine if we have valid sampling config (rules or fallback fraction)
241
257
  if head_sampling_config and (
242
- head_sampling_config.get('attributesAndSamplerRules') or
243
- head_sampling_config.get('fallbackFraction') is not None
258
+ head_sampling_config.get('noisyOperations') is not None
244
259
  ):
245
260
  valid_config = head_sampling_config
246
261
  else:
@@ -357,11 +372,19 @@ class ProxyResource(Resource):
357
372
 
358
373
  def _after_in_child():
359
374
  # This hook runs in every child process after fork().
360
- # It does two things:
361
- # 1. Marks ProxyResource as "dirty", so the next time spans are created,
362
- # the process.* attributes are recomputed with the child’s PID.
375
+ # It does three things:
376
+ # 1. Marks ProxyResource as "dirty" so process.* attributes are recomputed.
363
377
  # 2. Restarts the OpAMP client (threads/sockets do not survive fork).
378
+ # 3. Propagates the sampler to the new client so config updates reach it.
379
+ # Without this, the forked client's sampler is None and config updates
380
+ # from OpAMP heartbeats are silently dropped.
364
381
  provider = get_tracer_provider()
365
382
  if isinstance(getattr(provider, "resource", None), ProxyResource):
366
383
  provider.resource._child_refreshed = False
367
- start_opamp_client(OpampConnectionEvent())
384
+ client = start_opamp_client(OpampConnectionEvent())
385
+
386
+ if _odigos_sampler is not None:
387
+ client.sampler = _odigos_sampler
388
+ initial_config = client.get_initial_sampler_config()
389
+ if initial_config is not None:
390
+ _odigos_sampler.update_config(initial_config)
@@ -0,0 +1,168 @@
1
+ from opentelemetry.semconv.attributes import http_attributes as http_attributes_semconv
2
+ from opentelemetry.semconv.attributes import server_attributes as server_attributes_semconv
3
+ from opentelemetry.sdk.trace.sampling import Sampler, Decision, SamplingResult, _get_parent_trace_state
4
+ from threading import Lock
5
+ import logging
6
+
7
+
8
+ # Setup the Sampler logger
9
+ sampler_logger = logging.getLogger('odigos')
10
+
11
+ class OdigosSampler(Sampler):
12
+
13
+ # For compatibility with 64 bit trace IDs, the sampler checks the 64
14
+ # low-order bits of the trace ID to decide whether to sample a given trace.
15
+ TRACE_ID_LIMIT = (1 << 64) - 1
16
+
17
+ def __init__(self):
18
+ self._lock = Lock()
19
+ self._config = None
20
+
21
+ def _trace_id_based_sampling(self, trace_id: int, percentage_to_sample: float) -> bool:
22
+ """Mimic OpenTelemetry's trace ID-based sampling logic with 64-bit range."""
23
+ # Since percentage to sample is in percents and we need a fraction, divide by 100
24
+ fraction = percentage_to_sample / 100
25
+
26
+ # Calculate the bound based on the fraction using OpenTelemetry's approach
27
+ bound = round(fraction * (self.TRACE_ID_LIMIT + 1))
28
+ # Apply the TRACE_ID_LIMIT mask to ensure 64-bit range and compare it to the bound
29
+ return (trace_id & self.TRACE_ID_LIMIT) < bound
30
+
31
+ def should_sample(self, parent_context, trace_id, name, kind, attributes, links):
32
+ attributes = attributes or {}
33
+ with self._lock:
34
+ # sampler_logger.debug(f'Running Should_sample a span with the following attributes: {attributes}')
35
+
36
+ if self._config is None:
37
+ # sampler_logger.debug('No configuration is set, returning RECORD_AND_SAMPLE')
38
+ return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
39
+
40
+ noisy_operations = self._config.get('noisyOperations', [])
41
+ lowest_percentage = None
42
+
43
+ for noisy_operation in noisy_operations:
44
+ if noisy_operation.get("disabled"):
45
+ continue
46
+
47
+ percentage_to_sample = noisy_operation.get("percentageAtMost", 0.0)
48
+
49
+ http_operation_sample = noisy_operation.get("operation")
50
+ if not http_operation_sample:
51
+ continue
52
+
53
+ # sampler_logger.debug(f'Evaluating noisy operation: {noisy_operation}')
54
+
55
+ matched = False
56
+
57
+ if http_server_sample := http_operation_sample.get("httpServer"):
58
+ matched = self._match_http_server_sample_rule(http_server_sample, attributes) or matched
59
+
60
+ if http_client_sample := http_operation_sample.get("httpClient"):
61
+ matched = self._match_http_client_sample_rule(http_client_sample, attributes) or matched
62
+
63
+ # sampler_logger.debug(f'Noisy operation matched: {matched}, percentage: {percentage_to_sample}')
64
+ if matched: # For matched operations, compute the minimal lowest percentage
65
+ if lowest_percentage is None:
66
+ lowest_percentage = percentage_to_sample
67
+ else:
68
+ lowest_percentage = min(lowest_percentage, percentage_to_sample)
69
+
70
+ if lowest_percentage is not None:
71
+ if self._trace_id_based_sampling(trace_id, lowest_percentage):
72
+ # sampler_logger.debug(f'Trace [{trace_id}] is sampled with lowest percentage {lowest_percentage}')
73
+ return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
74
+ else:
75
+ # sampler_logger.debug(f'Trace [{trace_id}] is dropped with lowest percentage {lowest_percentage}')
76
+ return SamplingResult(Decision.DROP)
77
+
78
+ # sampler_logger.debug(f'No noisy operation matched, sampling the trace')
79
+ return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
80
+
81
+ @staticmethod
82
+ def _get_attribute(span_attributes: dict[str, str], *keys: str):
83
+ """Return the first non-None value found for the given attribute keys."""
84
+ for key in keys:
85
+ value = span_attributes.get(key)
86
+ if value is not None:
87
+ return value
88
+ return None
89
+
90
+ def _match_http_server_sample_rule(self, http_server_rule: dict[str, str], span_attributes: dict[str, str]) -> bool:
91
+ """
92
+ Try to match all the http server's attributes to the definitions of the noisy action
93
+ """
94
+ span_route = span_attributes.get(http_attributes_semconv.HTTP_ROUTE)
95
+ # Old semconv: "http.method" → new semconv: "http.request.method"
96
+ span_method = self._get_attribute(span_attributes, http_attributes_semconv.HTTP_REQUEST_METHOD, "http.method")
97
+
98
+ # Some instrumentations send target instead of route
99
+ target = self._get_attribute(span_attributes, "http.target", "url.path")
100
+
101
+ if http_server_rule.get("route") and http_server_rule["route"] != span_route:
102
+ # If we have route in the span attributes, evaluate it first (use template matching
103
+ # since param names may differ, e.g. /grml/{grml_id} vs /grml/{path_param})
104
+ if span_route:
105
+ return self._match_rule_route_to_span(http_server_rule["route"], span_route)
106
+ # If we have target, try to match it with the route in the sampling rule
107
+ if target:
108
+ return self._match_rule_route_to_span(http_server_rule["route"], target)
109
+
110
+ if http_server_rule.get("routePrefix") and not (span_route or "").startswith(http_server_rule["routePrefix"]):
111
+ return False
112
+ if http_server_rule.get("method") and http_server_rule["method"] != span_method:
113
+ return False
114
+
115
+ return True
116
+
117
+ def _match_http_client_sample_rule(self, http_client_rule: dict[str, str], span_attributes: dict[str, str]) -> bool:
118
+ """
119
+ Try to match all the http client's attributes to the definitions of the noisy action
120
+ """
121
+ # Old semconv: "net.peer.name" / "http.host" → new semconv: "server.address"
122
+ server_address = self._get_attribute(span_attributes, server_attributes_semconv.SERVER_ADDRESS, "net.peer.name", "http.host")
123
+ url_template = span_attributes.get("url.template")
124
+ # Old semconv: "http.method" → new semconv: "http.request.method"
125
+ method = self._get_attribute(span_attributes, http_attributes_semconv.HTTP_REQUEST_METHOD, "http.method")
126
+
127
+ if http_client_rule.get("serverAddress") and http_client_rule["serverAddress"] != server_address:
128
+ return False
129
+ if http_client_rule.get("templatedPath") and http_client_rule["templatedPath"] != url_template:
130
+ return False
131
+ if http_client_rule.get("templatedPathPrefix") and not (url_template or "").startswith(http_client_rule["templatedPathPrefix"]):
132
+ return False
133
+ if http_client_rule.get("method") and http_client_rule["method"] != method:
134
+ return False
135
+
136
+ return True
137
+
138
+ def _match_rule_route_to_span(self, rule_route: str, span_value: str) -> bool:
139
+ """
140
+ Match a sampling rule route (e.g. /item/{id}) against a span value, which can be either
141
+ a templated route (e.g. /item/{item_id}) or a full URL target (e.g. /item/123?param=hello).
142
+ Path param segments ({...}) in the rule are treated as wildcards.
143
+ """
144
+ span_value = span_value.split('?')[0]
145
+ rule_parts = [part for part in rule_route.split('/') if part]
146
+ span_parts = [part for part in span_value.split('/') if part]
147
+
148
+ if len(rule_parts) != len(span_parts):
149
+ return False
150
+
151
+ # Traverse both rule and span segments
152
+ for i in range(len(rule_parts)):
153
+ # If we hit a path param in the rule, it matches any span segment
154
+ if rule_parts[i].startswith('{'):
155
+ continue
156
+ if rule_parts[i] != span_parts[i]:
157
+ return False
158
+
159
+ return True
160
+
161
+
162
+ def get_description(self):
163
+ return "OdigosSampler"
164
+
165
+ def update_config(self, new_config):
166
+ with self._lock:
167
+ # sampler_logger.debug(f'Updating the configuration with the new configuration: {new_config}')
168
+ self._config = new_config
@@ -0,0 +1 @@
1
+ VERSION = "1.0.79"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odigos-opentelemetry-python
3
- Version: 1.0.77
3
+ Version: 1.0.79
4
4
  Summary: Odigos Initializer for Python OpenTelemetry Components
5
5
  Author-email: odigos-io <support@odigos.io>
6
6
  Requires-Python: >=3.9
@@ -9,7 +9,6 @@ initializer/lib_handling.py
9
9
  initializer/odigos_sampler.py
10
10
  initializer/post_instrument.py
11
11
  initializer/process_resource.py
12
- initializer/sampling_operators.py
13
12
  initializer/version.py
14
13
  initializer/distro/__init__.py
15
14
  initializer/distro/distro.py
@@ -452,8 +452,7 @@ class OpAMPHTTPClient:
452
452
 
453
453
  # Check if we have valid sampling config (same validation as update_agent_config)
454
454
  if head_sampling_config and (
455
- head_sampling_config.get('attributesAndSamplerRules') or
456
- head_sampling_config.get('fallbackFraction') is not None
455
+ head_sampling_config.get('noisyOperations') is not None
457
456
  ):
458
457
  return head_sampling_config
459
458
  else:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "odigos-opentelemetry-python"
7
- version = "1.0.77"
7
+ version = "1.0.79"
8
8
  description = "Odigos Initializer for Python OpenTelemetry Components"
9
9
  requires-python = ">=3.9"
10
10
  authors = [
@@ -1,98 +0,0 @@
1
- from .sampling_operators import SamplingOperators
2
- from opentelemetry.sdk.trace.sampling import Sampler, Decision, SamplingResult, _get_parent_trace_state
3
- from threading import Lock
4
- import logging
5
-
6
- # Setup the Sampler logger
7
- sampler_logger = logging.getLogger('odigos')
8
-
9
- class OdigosSampler(Sampler):
10
-
11
- # Define the sampling operator functions used in the configuration.
12
- # These operations are specified in the Odigos InstrumentationConfig Kubernetes object.
13
- _operations = {
14
- SamplingOperators.EQUALS.value: lambda attr, value: attr == value,
15
- SamplingOperators.NOT_EQUALS.value: lambda attr, value: attr != value,
16
- SamplingOperators.END_WITH.value: lambda attr, value: attr.endswith(value),
17
- SamplingOperators.START_WITH.value: lambda attr, value: attr.startswith(value)
18
- }
19
-
20
- # For compatibility with 64 bit trace IDs, the sampler checks the 64
21
- # low-order bits of the trace ID to decide whether to sample a given trace.
22
- TRACE_ID_LIMIT = (1 << 64) - 1
23
-
24
- def __init__(self):
25
- self._lock = Lock()
26
- self._config = None
27
-
28
- def _trace_id_based_sampling(self, trace_id, fraction):
29
- """Mimic OpenTelemetry's trace ID-based sampling logic with 64-bit range."""
30
- # Calculate the bound based on the fraction using OpenTelemetry's approach
31
- bound = round(fraction * (self.TRACE_ID_LIMIT + 1))
32
- # Apply the TRACE_ID_LIMIT mask to ensure 64-bit range and compare it to the bound
33
- return (trace_id & self.TRACE_ID_LIMIT) < bound
34
-
35
- def should_sample(self, parent_context, trace_id, name, kind, attributes, links):
36
- with self._lock:
37
- # sampler_logger.debug(f'Running Should_sample a span with the following attributes: {attributes}')
38
-
39
- if self._config is None:
40
- # sampler_logger.debug('No configuration is set, returning RECORD_AND_SAMPLE')
41
- return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
42
-
43
- rules = self._config.get('attributesAndSamplerRules', [])
44
- global_fraction = self._config.get('fallbackFraction', 1) # default to 1 if not set which means always sample
45
-
46
- for rule in rules: # The first attribute rule that evaluates to true is used to determine the sampling decision based on its fraction.
47
- and_attributes_sampler_rules = rule.get('attributeConditions', [])
48
-
49
- # sampler_logger.debug(f'"AND" rule operands are: {and_attributes_sampler_rules}')
50
-
51
- and_rule_fraction = rule.get('fraction', 1) # default to 1 if not set which means always sample
52
- and_rule_met = True
53
-
54
- for and_rule in and_attributes_sampler_rules:
55
- # If the "AND" rule is not met once, break the loop to avoid unnecessary checks
56
- if not and_rule_met:
57
- break
58
-
59
- key = and_rule.get('key')
60
- value = and_rule.get('val')
61
- operator = and_rule.get('operator') # equals / notEquals / endWith / startWith
62
-
63
- if key in attributes:
64
- attr_value = attributes[key]
65
- # Perform the corresponding operation
66
- if operator in self._operations and self._operations[operator](attr_value, value):
67
- pass # Condition matched
68
- else:
69
- # sampler_logger.debug(f'Operator {operator} is false, setting the "AND" rule flag to false')
70
- and_rule_met = False
71
- else:
72
- # sampler_logger.debug(f'Attribute {key} is not present in the span attributes, setting the "AND" rule flag to false')
73
- and_rule_met = False
74
-
75
- if and_rule_met:
76
- # Perform the sampling decision
77
- if self._trace_id_based_sampling(trace_id, and_rule_fraction):
78
- # sampler_logger.debug(f'Trace [{trace_id}] is sampled "And rules" are met with fraction {and_rule_fraction}')
79
- return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
80
- else:
81
- # sampler_logger.debug(f'Trace [{trace_id}] is dropped "And rules" are met but fraction {and_rule_fraction} not met')
82
- return SamplingResult(Decision.DROP)
83
-
84
- # Fallback to the global fraction if no rule matches
85
- # sampler_logger.debug(f'No rule matched, falling back to the global fraction {global_fraction}')
86
- if self._trace_id_based_sampling(trace_id, global_fraction):
87
- return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
88
- else:
89
- return SamplingResult(Decision.DROP)
90
-
91
-
92
- def get_description(self):
93
- return "OdigosSampler"
94
-
95
- def update_config(self, new_config):
96
- with self._lock:
97
- # sampler_logger.debug(f'Updating the configuration with the new configuration: {new_config}')
98
- self._config = new_config
@@ -1,7 +0,0 @@
1
- from enum import Enum
2
-
3
- class SamplingOperators(str, Enum):
4
- EQUALS = "equals"
5
- NOT_EQUALS = "notEquals"
6
- END_WITH = "endWith"
7
- START_WITH = "startWith"
@@ -1 +0,0 @@
1
- VERSION = "1.0.77"