wellapi 0.10.1__tar.gz → 0.10.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.
- {wellapi-0.10.1 → wellapi-0.10.2}/PKG-INFO +1 -1
- {wellapi-0.10.1 → wellapi-0.10.2}/docs/telemetry.md +30 -11
- {wellapi-0.10.1 → wellapi-0.10.2}/pyproject.toml +1 -1
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/telemetry/config.py +36 -1
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/telemetry/middleware.py +3 -12
- {wellapi-0.10.1 → wellapi-0.10.2}/tests/conftest.py +0 -13
- {wellapi-0.10.1 → wellapi-0.10.2}/tests/telemetry/test_config.py +39 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/tests/telemetry/test_middleware.py +3 -35
- {wellapi-0.10.1 → wellapi-0.10.2}/uv.lock +1 -1
- {wellapi-0.10.1 → wellapi-0.10.2}/.github/workflows/build.pipeline.yml +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/.gitignore +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/.python-version +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/README.md +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/docs/framework-usage.md +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/docs/superpowers/plans/2026-05-29-otel-native-telemetry.md +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/docs/superpowers/specs/2026-05-29-otel-native-telemetry-design.md +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/__init__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/__main__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/applications.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/awsmodel.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/build/__init__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/build/cdk.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/build/packager.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/cli/__init__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/cli/main.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/convertors.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/datastructures.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/dependencies/__init__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/dependencies/models.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/dependencies/utils.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/exception_handlers.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/exceptions.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/local/__init__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/local/router.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/local/server.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/middleware/__init__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/middleware/base.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/middleware/cors.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/middleware/error.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/middleware/exceptions.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/middleware/main.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/models.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/openapi/__init__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/openapi/docs.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/openapi/models.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/openapi/security_model.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/openapi/utils.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/params.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/routing.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/security.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/telemetry/__init__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/telemetry/attributes.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/telemetry/flush.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/testclient.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/src/wellapi/utils.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/tests/telemetry/__init__.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/tests/telemetry/test_applications.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/tests/telemetry/test_attributes.py +0 -0
- {wellapi-0.10.1 → wellapi-0.10.2}/tests/telemetry/test_flush.py +0 -0
|
@@ -74,17 +74,6 @@ SERVER GET /orders/{id}
|
|
|
74
74
|
Call `use_telemetry()` before `instrument()`. (Reverse order also works — OTel's
|
|
75
75
|
`ProxyTracer` resolves to the global provider at span-creation time.)
|
|
76
76
|
|
|
77
|
-
## Trace context propagation
|
|
78
|
-
|
|
79
|
-
Every invocation starts its **own** trace. The inbound W3C trace context — the
|
|
80
|
-
`traceparent` header on API Gateway requests, or the `traceparent` SQS message
|
|
81
|
-
attribute — is attached to the root span as a **span link**, not used as its
|
|
82
|
-
parent. This is deliberate: a sticky or shared upstream `traceparent` (e.g. a
|
|
83
|
-
client that reuses one trace across many calls, or a producer that fans out many
|
|
84
|
-
messages within a single trace) would otherwise collapse unrelated invocations
|
|
85
|
-
into a single `trace_id`. Linking instead of parenting keeps each invocation a
|
|
86
|
-
distinct trace while preserving the correlation back to the caller.
|
|
87
|
-
|
|
88
77
|
## Logs
|
|
89
78
|
|
|
90
79
|
wellapi configures and flushes a `LoggerProvider` but does not attach handlers —
|
|
@@ -118,6 +107,36 @@ to the function name).
|
|
|
118
107
|
`OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` override these without code
|
|
119
108
|
changes.
|
|
120
109
|
|
|
110
|
+
## SnapStart and ID uniqueness
|
|
111
|
+
|
|
112
|
+
When `warmup`/`use_snap_start` is on, the function is published with AWS Lambda
|
|
113
|
+
SnapStart: Lambda boots one environment, snapshots its memory, and restores that
|
|
114
|
+
**same** snapshot into every execution environment. Anything seeded once at init
|
|
115
|
+
is therefore shared by all restored environments.
|
|
116
|
+
|
|
117
|
+
OpenTelemetry's default ID generator draws trace/span IDs from Python's
|
|
118
|
+
module-level `random` (a Mersenne Twister seeded once at import). Under SnapStart
|
|
119
|
+
that frozen state is cloned, so every restored environment emits the **same
|
|
120
|
+
sequence** of trace/span IDs — you see one `trace_id` reused across unrelated
|
|
121
|
+
requests. wellapi avoids this by configuring the tracer provider with a
|
|
122
|
+
`SystemRandom` (`os.urandom`-backed) ID generator: the kernel CSPRNG is reseeded
|
|
123
|
+
with fresh entropy on restore, so IDs stay unique. No action needed for telemetry.
|
|
124
|
+
|
|
125
|
+
If your own handlers (or other libraries) rely on the `random` module, `uuid1`,
|
|
126
|
+
or any non-CSPRNG source for uniqueness, they have the same SnapStart hazard.
|
|
127
|
+
Reseed them in an after-restore hook:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import random
|
|
131
|
+
from snapshot_restore_py import register_after_restore
|
|
132
|
+
|
|
133
|
+
@register_after_restore
|
|
134
|
+
def _reseed():
|
|
135
|
+
random.seed() # pull fresh entropy from the OS after restore
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`os.urandom`, `secrets`, and `uuid4` read the kernel CSPRNG and are already safe.
|
|
139
|
+
|
|
121
140
|
## Local development
|
|
122
141
|
|
|
123
142
|
Telemetry is off until `use_telemetry()` is called. If you call it locally without
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import random
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
@@ -13,6 +14,37 @@ _INSTALL_HINT = (
|
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
class _SystemRandomIdGenerator:
|
|
18
|
+
"""Trace/span ID generator backed by the kernel CSPRNG (`os.urandom`).
|
|
19
|
+
|
|
20
|
+
OpenTelemetry's default `RandomIdGenerator` draws IDs from the module-level
|
|
21
|
+
Mersenne Twister, which Python seeds once at import. AWS Lambda SnapStart
|
|
22
|
+
captures that PRNG state in the snapshot and shares it across every restored
|
|
23
|
+
execution environment, so the default generator emits identical trace/span
|
|
24
|
+
IDs in lockstep across environments — observed as duplicate `trace_id`s on
|
|
25
|
+
unrelated requests. The kernel CSPRNG is reseeded with fresh entropy on
|
|
26
|
+
restore, so `random.SystemRandom` (i.e. `os.urandom`) stays unique. Duck-typed
|
|
27
|
+
against `opentelemetry.sdk.trace.id_generator.IdGenerator` to avoid importing
|
|
28
|
+
the SDK at module import time (kept lazy, like the rest of this module)."""
|
|
29
|
+
|
|
30
|
+
_rand = random.SystemRandom()
|
|
31
|
+
|
|
32
|
+
def generate_span_id(self) -> int:
|
|
33
|
+
span_id = self._rand.getrandbits(64)
|
|
34
|
+
while span_id == trace.INVALID_SPAN_ID:
|
|
35
|
+
span_id = self._rand.getrandbits(64)
|
|
36
|
+
return span_id
|
|
37
|
+
|
|
38
|
+
def generate_trace_id(self) -> int:
|
|
39
|
+
trace_id = self._rand.getrandbits(128)
|
|
40
|
+
while trace_id == trace.INVALID_TRACE_ID:
|
|
41
|
+
trace_id = self._rand.getrandbits(128)
|
|
42
|
+
return trace_id
|
|
43
|
+
|
|
44
|
+
def is_trace_id_random(self) -> bool:
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
16
48
|
@dataclass
|
|
17
49
|
class TelemetryHandle:
|
|
18
50
|
"""Handles to the configured providers. Returned by `app.use_telemetry()` so
|
|
@@ -73,7 +105,10 @@ def _build_providers(resource: Any) -> tuple[Any, Any, Any]:
|
|
|
73
105
|
|
|
74
106
|
# Exporters default to OTEL_EXPORTER_OTLP_ENDPOINT or http://localhost:4318,
|
|
75
107
|
# i.e. the collector-only Lambda layer running on localhost.
|
|
76
|
-
|
|
108
|
+
# _SystemRandomIdGenerator keeps IDs unique under SnapStart (see its docstring).
|
|
109
|
+
tracer_provider = TracerProvider(
|
|
110
|
+
resource=resource, id_generator=_SystemRandomIdGenerator()
|
|
111
|
+
)
|
|
77
112
|
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
|
|
78
113
|
|
|
79
114
|
meter_provider = MeterProvider(
|
|
@@ -4,8 +4,7 @@ import typing
|
|
|
4
4
|
|
|
5
5
|
try:
|
|
6
6
|
from opentelemetry import propagate
|
|
7
|
-
from opentelemetry.
|
|
8
|
-
from opentelemetry.trace import Link, Span, SpanKind, get_current_span
|
|
7
|
+
from opentelemetry.trace import Span, SpanKind
|
|
9
8
|
from opentelemetry.trace.status import Status, StatusCode
|
|
10
9
|
except ImportError as err: # pragma: no cover
|
|
11
10
|
raise RuntimeError(
|
|
@@ -68,14 +67,7 @@ class TelemetryMiddleware:
|
|
|
68
67
|
start = time.perf_counter()
|
|
69
68
|
|
|
70
69
|
attribute = get_request_attribute(request)
|
|
71
|
-
|
|
72
|
-
# context is attached as a link, not used as the parent: a sticky or
|
|
73
|
-
# shared upstream traceparent would otherwise collapse unrelated
|
|
74
|
-
# invocations into a single trace_id (warm-container fan-in).
|
|
75
|
-
upstream = get_current_span(
|
|
76
|
-
propagate.extract(get_trace_carrier(request))
|
|
77
|
-
).get_span_context()
|
|
78
|
-
links = [Link(upstream)] if upstream.is_valid else None
|
|
70
|
+
parent = propagate.extract(get_trace_carrier(request))
|
|
79
71
|
span_attributes = {
|
|
80
72
|
**attribute.attributes,
|
|
81
73
|
**get_code_attribute(),
|
|
@@ -86,10 +78,9 @@ class TelemetryMiddleware:
|
|
|
86
78
|
response: ResponseAPIGateway | None = None
|
|
87
79
|
with self.tracer.start_as_current_span(
|
|
88
80
|
attribute.span_name,
|
|
89
|
-
context=
|
|
81
|
+
context=parent,
|
|
90
82
|
kind=_SPAN_KIND.get(attribute.kind, SpanKind.SERVER),
|
|
91
83
|
attributes=span_attributes,
|
|
92
|
-
links=links,
|
|
93
84
|
) as span:
|
|
94
85
|
if self.request_hook:
|
|
95
86
|
self.request_hook(span, request)
|
|
@@ -83,24 +83,11 @@ def job_event():
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
@pytest.fixture
|
|
87
|
-
def api_gateway_event_no_trace(api_gateway_event):
|
|
88
|
-
event = dict(api_gateway_event)
|
|
89
|
-
event["headers"] = {"Host": "api.example.com"}
|
|
90
|
-
event["multiValueHeaders"] = {"Host": ["api.example.com"]}
|
|
91
|
-
return event
|
|
92
|
-
|
|
93
|
-
|
|
94
86
|
@pytest.fixture
|
|
95
87
|
def api_gateway_request(api_gateway_event):
|
|
96
88
|
return RequestAPIGateway.create_request_from_event(api_gateway_event)
|
|
97
89
|
|
|
98
90
|
|
|
99
|
-
@pytest.fixture
|
|
100
|
-
def api_gateway_request_no_trace(api_gateway_event_no_trace):
|
|
101
|
-
return RequestAPIGateway.create_request_from_event(api_gateway_event_no_trace)
|
|
102
|
-
|
|
103
|
-
|
|
104
91
|
@pytest.fixture
|
|
105
92
|
def sqs_request(sqs_event):
|
|
106
93
|
return RequestSQS.create_request_from_event(sqs_event)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
1
3
|
import wellapi.telemetry.config as config_mod
|
|
2
4
|
from wellapi.telemetry.config import TelemetryHandle, _build_resource
|
|
3
5
|
|
|
@@ -6,6 +8,40 @@ def _attrs(resource):
|
|
|
6
8
|
return dict(resource.attributes)
|
|
7
9
|
|
|
8
10
|
|
|
11
|
+
def _ids_after_seed(generator, seed):
|
|
12
|
+
"""IDs a freshly snapshot-restored env would emit: identical frozen
|
|
13
|
+
module-level random state (the AWS SnapStart failure mode)."""
|
|
14
|
+
state = random.getstate()
|
|
15
|
+
try:
|
|
16
|
+
random.seed(seed)
|
|
17
|
+
return generator.generate_trace_id(), generator.generate_span_id()
|
|
18
|
+
finally:
|
|
19
|
+
random.setstate(state)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_default_random_id_generator_collides_under_cloned_random_state():
|
|
23
|
+
# Documents the bug: the OTel default generator draws from the module-level
|
|
24
|
+
# Mersenne Twister, which SnapStart freezes and shares across restored envs.
|
|
25
|
+
from opentelemetry.sdk.trace.id_generator import RandomIdGenerator
|
|
26
|
+
|
|
27
|
+
gen = RandomIdGenerator()
|
|
28
|
+
assert _ids_after_seed(gen, 1234) == _ids_after_seed(gen, 1234)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_snapstart_safe_id_generator_survives_cloned_random_state():
|
|
32
|
+
gen = config_mod._SystemRandomIdGenerator()
|
|
33
|
+
# Two envs restored from the same snapshot (same frozen MT state) must still
|
|
34
|
+
# produce distinct trace/span IDs.
|
|
35
|
+
assert _ids_after_seed(gen, 1234) != _ids_after_seed(gen, 1234)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_snapstart_safe_id_generator_marks_trace_id_random():
|
|
39
|
+
gen = config_mod._SystemRandomIdGenerator()
|
|
40
|
+
assert gen.is_trace_id_random() is True
|
|
41
|
+
assert 0 < gen.generate_trace_id() < 2**128
|
|
42
|
+
assert 0 < gen.generate_span_id() < 2**64
|
|
43
|
+
|
|
44
|
+
|
|
9
45
|
def test_resource_uses_faas_env(monkeypatch):
|
|
10
46
|
monkeypatch.setenv("AWS_LAMBDA_FUNCTION_NAME", "orders-fn")
|
|
11
47
|
monkeypatch.setenv("AWS_REGION", "eu-west-1")
|
|
@@ -53,6 +89,9 @@ def test_build_providers_attaches_resource_and_returns_sdk_types():
|
|
|
53
89
|
assert isinstance(mp, MeterProvider)
|
|
54
90
|
assert isinstance(lp, LoggerProvider)
|
|
55
91
|
assert dict(tp.resource.attributes)["service.name"] == "t"
|
|
92
|
+
# SnapStart-safe IDs: the tracer provider must not use the default MT-backed
|
|
93
|
+
# generator, otherwise restored envs emit duplicate trace/span IDs.
|
|
94
|
+
assert isinstance(tp.id_generator, config_mod._SystemRandomIdGenerator)
|
|
56
95
|
|
|
57
96
|
|
|
58
97
|
def test_configure_reuses_existing_sdk_provider(monkeypatch):
|
|
@@ -63,7 +63,7 @@ def test_span_is_active_in_context(telemetry, api_gateway_request, monkeypatch):
|
|
|
63
63
|
assert captured["ctx"].span_id == span.context.span_id
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def
|
|
66
|
+
def test_parent_context_extracted_from_traceparent(
|
|
67
67
|
telemetry, api_gateway_request, monkeypatch
|
|
68
68
|
):
|
|
69
69
|
handle, exporter, _ = telemetry
|
|
@@ -72,40 +72,8 @@ def test_inbound_traceparent_becomes_link_not_parent(
|
|
|
72
72
|
api_gateway_request
|
|
73
73
|
)
|
|
74
74
|
(span,) = exporter.get_finished_spans()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
assert span.context.trace_id != TRACE_ID
|
|
78
|
-
assert span.parent is None
|
|
79
|
-
# The upstream context is preserved as a link for cross-trace correlation.
|
|
80
|
-
(link,) = span.links
|
|
81
|
-
assert link.context.trace_id == TRACE_ID
|
|
82
|
-
assert link.context.span_id == PARENT_SPAN_ID
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def test_two_invocations_with_same_traceparent_get_distinct_traces(
|
|
86
|
-
telemetry, api_gateway_request, monkeypatch
|
|
87
|
-
):
|
|
88
|
-
handle, exporter, _ = telemetry
|
|
89
|
-
monkeypatch.setattr(mw_mod, "force_flush", lambda *a, **k: None)
|
|
90
|
-
mw = TelemetryMiddleware(lambda req: ResponseAPIGateway(status_code=200), handle)
|
|
91
|
-
mw(api_gateway_request)
|
|
92
|
-
mw(api_gateway_request)
|
|
93
|
-
trace_ids = {s.context.trace_id for s in exporter.get_finished_spans()}
|
|
94
|
-
assert len(trace_ids) == 2
|
|
95
|
-
assert TRACE_ID not in trace_ids
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def test_no_inbound_traceparent_starts_root_without_link(
|
|
99
|
-
telemetry, api_gateway_request_no_trace, monkeypatch
|
|
100
|
-
):
|
|
101
|
-
handle, exporter, _ = telemetry
|
|
102
|
-
monkeypatch.setattr(mw_mod, "force_flush", lambda *a, **k: None)
|
|
103
|
-
TelemetryMiddleware(lambda req: ResponseAPIGateway(status_code=200), handle)(
|
|
104
|
-
api_gateway_request_no_trace
|
|
105
|
-
)
|
|
106
|
-
(span,) = exporter.get_finished_spans()
|
|
107
|
-
assert span.parent is None
|
|
108
|
-
assert span.links == ()
|
|
75
|
+
assert span.context.trace_id == TRACE_ID
|
|
76
|
+
assert span.parent.span_id == PARENT_SPAN_ID
|
|
109
77
|
|
|
110
78
|
|
|
111
79
|
def test_exception_sets_error_status_and_reraises(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wellapi-0.10.1 → wellapi-0.10.2}/docs/superpowers/plans/2026-05-29-otel-native-telemetry.md
RENAMED
|
File without changes
|
{wellapi-0.10.1 → wellapi-0.10.2}/docs/superpowers/specs/2026-05-29-otel-native-telemetry-design.md
RENAMED
|
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
|
|
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
|