datashare-python 0.9.5__tar.gz → 0.9.7__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.
- {datashare_python-0.9.5 → datashare_python-0.9.7}/PKG-INFO +1 -3
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/conftest.py +26 -18
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/interceptors.py +37 -8
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/logging_.py +2 -2
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/types_.py +20 -4
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/utils.py +31 -11
- datashare_python-0.9.7/datashare_python/worker-template.tar.gz +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/worker.py +4 -1
- {datashare_python-0.9.5 → datashare_python-0.9.7}/pyproject.toml +2 -3
- datashare_python-0.9.5/datashare_python/worker-template.tar.gz +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/.gitignore +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/README.md +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/.gitignore +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/__init__.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/__main__.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/cli/__init__.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/cli/project.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/cli/task.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/cli/utils.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/cli/worker.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/config.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/constants.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/dependencies.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/discovery.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/exceptions.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/objects.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/task_client.py +0 -0
- {datashare_python-0.9.5 → datashare_python-0.9.7}/datashare_python/template.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datashare-python
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.7
|
|
4
4
|
Summary: Manage Python tasks and local resources in Datashare
|
|
5
5
|
Project-URL: Homepage, https://icij.github.io/datashare-python/
|
|
6
6
|
Project-URL: Documentation, https://icij.github.io/datashare-python/
|
|
@@ -14,8 +14,6 @@ Requires-Dist: hatchling~=1.27
|
|
|
14
14
|
Requires-Dist: icij-common[elasticsearch]~=0.8.2
|
|
15
15
|
Requires-Dist: langcodes~=3.5
|
|
16
16
|
Requires-Dist: lru-dict~=1.4
|
|
17
|
-
Requires-Dist: nest-asyncio~=1.6
|
|
18
|
-
Requires-Dist: orjson~=3.11
|
|
19
17
|
Requires-Dist: pydantic-extra-types[pycountry]>=2.11.1
|
|
20
18
|
Requires-Dist: python-json-logger~=4.0
|
|
21
19
|
Requires-Dist: pyyaml~=6.0
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import shutil
|
|
3
|
-
from
|
|
4
|
-
from collections.abc import AsyncGenerator, Generator, Iterator, Sequence
|
|
2
|
+
from collections.abc import AsyncGenerator, Generator, Sequence
|
|
5
3
|
from pathlib import Path
|
|
6
4
|
|
|
7
5
|
import aiohttp
|
|
@@ -10,7 +8,9 @@ import pytest
|
|
|
10
8
|
from elasticsearch._async.helpers import async_streaming_bulk
|
|
11
9
|
from icij_common.es import DOC_ROOT_ID, ES_DOCUMENT_TYPE, ID, ESClient
|
|
12
10
|
from icij_common.test_utils import reset_env # noqa: F401
|
|
11
|
+
from pytest_asyncio import is_async_test
|
|
13
12
|
from temporalio import workflow
|
|
13
|
+
from temporalio.service import RPCError, RPCStatusCode
|
|
14
14
|
|
|
15
15
|
from datashare_python.config import (
|
|
16
16
|
DatashareClientConfig,
|
|
@@ -59,6 +59,13 @@ _INDEX_BODY = {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
def pytest_collection_modifyitems(items: list) -> None:
|
|
63
|
+
pytest_asyncio_tests = (item for item in items if is_async_test(item))
|
|
64
|
+
session_scope_marker = pytest.mark.asyncio(loop_scope="session")
|
|
65
|
+
for async_test in pytest_asyncio_tests:
|
|
66
|
+
async_test.add_marker(session_scope_marker, append=False)
|
|
67
|
+
|
|
68
|
+
|
|
62
69
|
@activity_defn(name="mocked-act")
|
|
63
70
|
def mocked_act() -> None:
|
|
64
71
|
pass
|
|
@@ -81,16 +88,6 @@ def test_deps() -> list[ContextManagerFactory]:
|
|
|
81
88
|
return [set_es_client, set_task_client]
|
|
82
89
|
|
|
83
90
|
|
|
84
|
-
@pytest.fixture(scope="session")
|
|
85
|
-
def event_loop(
|
|
86
|
-
request: pytest.FixtureRequest, # noqa: ARG001
|
|
87
|
-
) -> Iterator[asyncio.AbstractEventLoop]:
|
|
88
|
-
"""Create an instance of the default event loop for each test case."""
|
|
89
|
-
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
90
|
-
yield loop
|
|
91
|
-
loop.close()
|
|
92
|
-
|
|
93
|
-
|
|
94
91
|
@pytest.fixture(scope="session")
|
|
95
92
|
def test_worker_config() -> WorkerConfig:
|
|
96
93
|
logging_config = LoggingConfig(
|
|
@@ -117,9 +114,7 @@ def test_worker_config_path(test_worker_config: WorkerConfig, tmpdir: Path) -> P
|
|
|
117
114
|
|
|
118
115
|
@pytest.fixture(scope="session")
|
|
119
116
|
async def worker_lifetime_deps(
|
|
120
|
-
|
|
121
|
-
test_deps: list[ContextManagerFactory],
|
|
122
|
-
test_worker_config: WorkerConfig,
|
|
117
|
+
test_deps: list[ContextManagerFactory], test_worker_config: WorkerConfig
|
|
123
118
|
) -> AsyncGenerator[None, None]:
|
|
124
119
|
worker_id = "test-worker-id"
|
|
125
120
|
ctx = "test application"
|
|
@@ -128,7 +123,6 @@ async def worker_lifetime_deps(
|
|
|
128
123
|
ctx=ctx,
|
|
129
124
|
worker_id=worker_id,
|
|
130
125
|
worker_config=test_worker_config,
|
|
131
|
-
event_loop=event_loop,
|
|
132
126
|
):
|
|
133
127
|
yield
|
|
134
128
|
|
|
@@ -174,11 +168,25 @@ async def test_task_client(
|
|
|
174
168
|
@pytest.fixture(scope="session")
|
|
175
169
|
async def test_temporal_client_session(
|
|
176
170
|
test_worker_config: WorkerConfig,
|
|
177
|
-
event_loop: AbstractEventLoop, # noqa: ARG001
|
|
178
171
|
) -> TemporalClient: # noqa: ANN001
|
|
179
172
|
return await test_worker_config.to_temporal_client()
|
|
180
173
|
|
|
181
174
|
|
|
175
|
+
@pytest.fixture
|
|
176
|
+
async def test_temporal_client(
|
|
177
|
+
test_temporal_client_session: TemporalClient,
|
|
178
|
+
) -> TemporalClient: # noqa: ANN001
|
|
179
|
+
client = test_temporal_client_session
|
|
180
|
+
async for wf in client.list_workflows():
|
|
181
|
+
try:
|
|
182
|
+
await client.get_workflow_handle(wf.id).terminate()
|
|
183
|
+
except RPCError as e:
|
|
184
|
+
if e.status != RPCStatusCode.NOT_FOUND:
|
|
185
|
+
raise
|
|
186
|
+
|
|
187
|
+
return client
|
|
188
|
+
|
|
189
|
+
|
|
182
190
|
@pytest.fixture
|
|
183
191
|
async def populate_es(
|
|
184
192
|
test_es_client: ESClient,
|
|
@@ -6,7 +6,7 @@ from collections.abc import Callable, Generator, Mapping
|
|
|
6
6
|
from contextlib import contextmanager
|
|
7
7
|
from contextvars import ContextVar
|
|
8
8
|
from copy import deepcopy
|
|
9
|
-
from functools import partial
|
|
9
|
+
from functools import partial, wraps
|
|
10
10
|
from inspect import signature
|
|
11
11
|
from types import UnionType
|
|
12
12
|
from typing import (
|
|
@@ -52,7 +52,12 @@ from temporalio.workflow import (
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
from .objects import BaseModel
|
|
55
|
-
from .types_ import
|
|
55
|
+
from .types_ import (
|
|
56
|
+
AsyncProgressRateHandler,
|
|
57
|
+
ProgressRateHandler,
|
|
58
|
+
SyncProgressRateHandler,
|
|
59
|
+
Weight,
|
|
60
|
+
)
|
|
56
61
|
from .utils import (
|
|
57
62
|
PROGRESS_HANDLER_ARG,
|
|
58
63
|
PYDANTIC_DATA_CONVERTER,
|
|
@@ -62,6 +67,11 @@ from .utils import (
|
|
|
62
67
|
|
|
63
68
|
_TRACEPARENT = "traceparent"
|
|
64
69
|
_DEFAULT_PAYLOAD_CONVERTER = DataConverter.default.payload_converter
|
|
70
|
+
_PROGRESS_TYPES = {
|
|
71
|
+
ProgressRateHandler,
|
|
72
|
+
AsyncProgressRateHandler,
|
|
73
|
+
SyncProgressRateHandler,
|
|
74
|
+
}
|
|
65
75
|
|
|
66
76
|
|
|
67
77
|
class TraceContext(BaseModel):
|
|
@@ -292,11 +302,13 @@ def _get_progress_handler(act_fn: Callable) -> ProgressRateHandler:
|
|
|
292
302
|
|
|
293
303
|
|
|
294
304
|
def _is_progress(t: type) -> bool:
|
|
295
|
-
if t is
|
|
305
|
+
if any(t is p_cls for p_cls in _PROGRESS_TYPES):
|
|
296
306
|
return True
|
|
297
307
|
return bool(
|
|
298
308
|
isinstance(t, UnionType)
|
|
299
|
-
and any(
|
|
309
|
+
and any(
|
|
310
|
+
any(sub_t is p_cls for p_cls in _PROGRESS_TYPES) for sub_t in get_args(t)
|
|
311
|
+
)
|
|
300
312
|
)
|
|
301
313
|
|
|
302
314
|
|
|
@@ -315,16 +327,21 @@ class _ProgressInboundInterceptor(ActivityInboundInterceptor):
|
|
|
315
327
|
# https://github.com/temporalio/sdk-python/blob/631ebaf0e20fb214b16589b45627b358048a5d77/temporalio/worker/_activity.py#L600
|
|
316
328
|
# we have to force it here again
|
|
317
329
|
progress_handler = _get_progress_handler(input.fn)
|
|
330
|
+
new_args = []
|
|
331
|
+
act_definition = _Definition.must_from_callable(input.fn)
|
|
318
332
|
if input.args:
|
|
319
333
|
data_converter = PYDANTIC_DATA_CONVERTER
|
|
320
|
-
arg_types =
|
|
334
|
+
arg_types = act_definition.arg_types
|
|
321
335
|
arg_types = _without_progress(arg_types)
|
|
322
336
|
arg_types = arg_types[: len(input.args)]
|
|
323
337
|
encoded = await data_converter.encode(input.args)
|
|
324
338
|
new_args = await data_converter.decode(encoded, type_hints=arg_types)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
339
|
+
injected_progress = (
|
|
340
|
+
progress_handler
|
|
341
|
+
if act_definition.is_async
|
|
342
|
+
else _sync_progress(progress_handler)
|
|
343
|
+
)
|
|
344
|
+
new_args.append(injected_progress)
|
|
328
345
|
new_input = dataclasses.replace(input, args=new_args)
|
|
329
346
|
await progress_handler(0.0)
|
|
330
347
|
res = await super().execute_activity(new_input)
|
|
@@ -374,3 +391,15 @@ class _HeartbeatInboundInterceptor(ActivityInboundInterceptor):
|
|
|
374
391
|
if heartbeat_task:
|
|
375
392
|
heartbeat_task.cancel()
|
|
376
393
|
await asyncio.wait([heartbeat_task])
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _sync_progress(
|
|
397
|
+
progress_handler: AsyncProgressRateHandler,
|
|
398
|
+
) -> SyncProgressRateHandler:
|
|
399
|
+
@wraps(progress_handler)
|
|
400
|
+
def p(progress: float, event_loop: asyncio.AbstractEventLoop) -> None:
|
|
401
|
+
asyncio.run_coroutine_threadsafe(
|
|
402
|
+
progress_handler(progress), event_loop
|
|
403
|
+
).result()
|
|
404
|
+
|
|
405
|
+
return p
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
2
3
|
import numbers
|
|
3
4
|
import sys
|
|
4
5
|
from copy import copy
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
|
-
import orjson
|
|
8
8
|
from icij_common.logging_utils import DATE_FMT, STREAM_HANDLER_FMT
|
|
9
9
|
from pythonjsonlogger.core import BaseJsonFormatter
|
|
10
10
|
from pythonjsonlogger.orjson import OrjsonFormatter
|
|
@@ -132,7 +132,7 @@ def _encode_value(value: Any) -> str:
|
|
|
132
132
|
return "true" if value else "false"
|
|
133
133
|
if isinstance(value, numbers.Number):
|
|
134
134
|
return str(value)
|
|
135
|
-
return
|
|
135
|
+
return json.dumps(value).decode()
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
def _json_formatter(datefmt: str) -> BaseJsonFormatter:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from collections.abc import Coroutine
|
|
2
3
|
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
|
3
4
|
from dataclasses import dataclass
|
|
@@ -8,19 +9,34 @@ from temporalio.client import Client
|
|
|
8
9
|
TemporalClient = Client
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
class
|
|
12
|
+
class AsyncProgressRateHandler(Protocol):
|
|
12
13
|
async def __call__(self, progress_rate: float) -> None:
|
|
13
14
|
pass
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
class SyncProgressRateHandler(Protocol):
|
|
18
|
+
def __call__(
|
|
19
|
+
self, progress_rate: float, event_loop: asyncio.AbstractEventLoop
|
|
20
|
+
) -> None:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
ProgressRateHandler = SyncProgressRateHandler | AsyncProgressRateHandler
|
|
25
|
+
|
|
26
|
+
|
|
16
27
|
@dataclass
|
|
17
28
|
class Weight:
|
|
18
29
|
value: float
|
|
19
30
|
|
|
20
31
|
|
|
21
|
-
class
|
|
22
|
-
async def __call__(self, iteration: int) -> None:
|
|
23
|
-
|
|
32
|
+
class RawAsyncProgressHandler(Protocol):
|
|
33
|
+
async def __call__(self, iteration: int) -> None: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RawSyncProgressHandler(Protocol):
|
|
37
|
+
async def __call__(
|
|
38
|
+
self, iteration: int, event_loop: asyncio.AbstractEventLoop
|
|
39
|
+
) -> None: ...
|
|
24
40
|
|
|
25
41
|
|
|
26
42
|
FactoryReturnType = (
|
|
@@ -15,7 +15,6 @@ from pathlib import Path
|
|
|
15
15
|
from typing import Any, ParamSpec, TypeVar
|
|
16
16
|
from uuid import uuid4
|
|
17
17
|
|
|
18
|
-
import nest_asyncio
|
|
19
18
|
import temporalio
|
|
20
19
|
from pydantic import ValidationError
|
|
21
20
|
from temporalio import activity, workflow
|
|
@@ -31,9 +30,15 @@ from temporalio.converter import (
|
|
|
31
30
|
)
|
|
32
31
|
from temporalio.exceptions import ApplicationError
|
|
33
32
|
|
|
33
|
+
from datashare_python.types_ import (
|
|
34
|
+
AsyncProgressRateHandler,
|
|
35
|
+
RawSyncProgressHandler,
|
|
36
|
+
SyncProgressRateHandler,
|
|
37
|
+
)
|
|
38
|
+
|
|
34
39
|
from .constants import METADATA_JSON
|
|
35
40
|
from .objects import DocArtifact, DocumentLocation, FilesystemDocument
|
|
36
|
-
from .types_ import
|
|
41
|
+
from .types_ import RawAsyncProgressHandler
|
|
37
42
|
|
|
38
43
|
DependencyLabel = str | None
|
|
39
44
|
DependencySetup = Callable[..., None]
|
|
@@ -76,10 +81,13 @@ class ProgressSignal:
|
|
|
76
81
|
|
|
77
82
|
|
|
78
83
|
class ActivityWithProgress:
|
|
79
|
-
def __init__(
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
temporal_client: Client,
|
|
87
|
+
event_loop: asyncio.AbstractEventLoop | None = None,
|
|
88
|
+
):
|
|
80
89
|
self._temporal_client = temporal_client
|
|
81
|
-
|
|
82
|
-
self._event_loop = event_loop
|
|
90
|
+
self._event_loop = event_loop or asyncio.get_event_loop()
|
|
83
91
|
|
|
84
92
|
|
|
85
93
|
class WorkflowWithProgress:
|
|
@@ -276,9 +284,9 @@ def fatal_error_from_exception(exc: Exception) -> ApplicationError:
|
|
|
276
284
|
return ApplicationError(str(exc), details, type=exc_type, non_retryable=True)
|
|
277
285
|
|
|
278
286
|
|
|
279
|
-
def
|
|
280
|
-
progress:
|
|
281
|
-
) ->
|
|
287
|
+
def to_raw_async_progress(
|
|
288
|
+
progress: AsyncProgressRateHandler, max_progress: int
|
|
289
|
+
) -> RawAsyncProgressHandler:
|
|
282
290
|
if not max_progress > 0:
|
|
283
291
|
raise ValueError("max_progress must be > 0")
|
|
284
292
|
|
|
@@ -288,9 +296,21 @@ def to_raw_progress(
|
|
|
288
296
|
return raw
|
|
289
297
|
|
|
290
298
|
|
|
291
|
-
def
|
|
292
|
-
progress:
|
|
293
|
-
) ->
|
|
299
|
+
def to_raw_sync_progress(
|
|
300
|
+
progress: SyncProgressRateHandler, max_progress: int
|
|
301
|
+
) -> RawSyncProgressHandler:
|
|
302
|
+
if not max_progress > 0:
|
|
303
|
+
raise ValueError("max_progress must be > 0")
|
|
304
|
+
|
|
305
|
+
def raw(iteration: int, event_loop: asyncio.AbstractEventLoop) -> None:
|
|
306
|
+
progress(iteration / max_progress, event_loop)
|
|
307
|
+
|
|
308
|
+
return raw
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def to_scaled_async_progress(
|
|
312
|
+
progress: AsyncProgressRateHandler, *, start: float = 0.0, end: float = 1.0
|
|
313
|
+
) -> AsyncProgressRateHandler:
|
|
294
314
|
if not 0 <= start < end:
|
|
295
315
|
raise ValueError("start must be [0, end[")
|
|
296
316
|
if not start < end <= 1.0:
|
|
Binary file
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import inspect
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
@@ -157,7 +158,7 @@ async def worker_context(
|
|
|
157
158
|
workflows: list[type] | None = None,
|
|
158
159
|
worker_config: WorkerConfig,
|
|
159
160
|
client: TemporalClient,
|
|
160
|
-
event_loop: AbstractEventLoop,
|
|
161
|
+
event_loop: AbstractEventLoop | None = None,
|
|
161
162
|
task_queue: str,
|
|
162
163
|
dependencies: list[ContextManagerFactory] | None = None,
|
|
163
164
|
sandboxed: bool = True,
|
|
@@ -169,6 +170,8 @@ async def worker_context(
|
|
|
169
170
|
discovered.extend(workflows)
|
|
170
171
|
if dependencies is not None:
|
|
171
172
|
discovered.extend(dependencies)
|
|
173
|
+
if event_loop is None:
|
|
174
|
+
event_loop = asyncio.get_event_loop()
|
|
172
175
|
discovered.append(worker_config)
|
|
173
176
|
loggers = copy(worker_config.logging.loggers)
|
|
174
177
|
discovered_loggers = {_get_object_package(o).__name__ for o in discovered}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "datashare-python"
|
|
3
|
-
version = "0.9.
|
|
3
|
+
version = "0.9.7"
|
|
4
4
|
description = "Manage Python tasks and local resources in Datashare"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Clément Doumouro", email = "cdoumouro@icij.org" },
|
|
@@ -15,8 +15,6 @@ dependencies = [
|
|
|
15
15
|
"hatchling~=1.27",
|
|
16
16
|
"icij-common[elasticsearch]~=0.8.2",
|
|
17
17
|
"langcodes~=3.5",
|
|
18
|
-
"nest-asyncio~=1.6",
|
|
19
|
-
"orjson~=3.11",
|
|
20
18
|
"python-json-logger~=4.0",
|
|
21
19
|
"pyyaml~=6.0",
|
|
22
20
|
"pydantic-extra-types[pycountry]>=2.11.1",
|
|
@@ -76,6 +74,7 @@ dev = [
|
|
|
76
74
|
|
|
77
75
|
[tool.pytest.ini_options]
|
|
78
76
|
asyncio_mode = "auto"
|
|
77
|
+
asyncio_debug = true
|
|
79
78
|
asyncio_default_fixture_loop_scope = "session"
|
|
80
79
|
markers = [
|
|
81
80
|
"integration",
|
|
Binary file
|
|
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
|