dagster-azure 0.27.15__tar.gz → 0.28.0__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.
- {dagster_azure-0.27.15/dagster_azure.egg-info → dagster_azure-0.28.0}/PKG-INFO +3 -2
- dagster_azure-0.28.0/dagster_azure/pipes/__init__.py +9 -0
- dagster_azure-0.28.0/dagster_azure/pipes/clients/__init__.py +5 -0
- dagster_azure-0.28.0/dagster_azure/pipes/clients/azureml.py +140 -0
- dagster_azure-0.28.0/dagster_azure/pipes/context_injectors.py +47 -0
- dagster_azure-0.28.0/dagster_azure/pipes/message_readers.py +83 -0
- dagster_azure-0.28.0/dagster_azure/version.py +1 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0/dagster_azure.egg-info}/PKG-INFO +3 -2
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure.egg-info/SOURCES.txt +6 -1
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure.egg-info/requires.txt +2 -1
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/setup.py +2 -1
- dagster_azure-0.27.15/dagster_azure/version.py +0 -1
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/LICENSE +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/MANIFEST.in +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/README.md +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/__init__.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/adls2/__init__.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/adls2/file_manager.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/adls2/io_manager.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/adls2/resources.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/adls2/utils.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/blob/__init__.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/blob/compute_log_manager.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/blob/fake_blob_client.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/blob/resources.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/blob/utils.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/fakes/__init__.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/fakes/fake_adls2_resource.py +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure/py.typed +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure.egg-info/dependency_links.txt +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure.egg-info/entry_points.txt +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure.egg-info/not-zip-safe +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/dagster_azure.egg-info/top_level.txt +0 -0
- {dagster_azure-0.27.15 → dagster_azure-0.28.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dagster-azure
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.28.0
|
|
4
4
|
Summary: Package for Azure-specific Dagster framework op and resource components.
|
|
5
5
|
Home-page: https://github.com/dagster-io/dagster/tree/master/python_modules/libraries/dagster-azure
|
|
6
6
|
Author: Dagster Labs
|
|
@@ -17,9 +17,10 @@ Requires-Python: >=3.9,<3.14
|
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: azure-core<2.0.0,>=1.7.0
|
|
19
19
|
Requires-Dist: azure-identity<2.0.0,>=1.7.0
|
|
20
|
+
Requires-Dist: azure-ai-ml<2.0.0,>=1.28.0
|
|
20
21
|
Requires-Dist: azure-storage-blob<13.0.0,>=12.5.0
|
|
21
22
|
Requires-Dist: azure-storage-file-datalake<13.0.0,>=12.5
|
|
22
|
-
Requires-Dist: dagster==1.
|
|
23
|
+
Requires-Dist: dagster==1.12.0
|
|
23
24
|
Dynamic: author
|
|
24
25
|
Dynamic: author-email
|
|
25
26
|
Dynamic: classifier
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from dagster_azure.pipes.clients import PipesAzureMLClient
|
|
2
|
+
from dagster_azure.pipes.context_injectors import PipesAzureBlobStorageContextInjector
|
|
3
|
+
from dagster_azure.pipes.message_readers import PipesAzureBlobStorageMessageReader
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"PipesAzureBlobStorageContextInjector",
|
|
7
|
+
"PipesAzureBlobStorageMessageReader",
|
|
8
|
+
"PipesAzureMLClient",
|
|
9
|
+
]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
import dagster._check as check
|
|
5
|
+
from azure.ai.ml import MLClient
|
|
6
|
+
from azure.ai.ml.entities import Command
|
|
7
|
+
from dagster._core.definitions.resource_annotation import TreatAsResourceParam
|
|
8
|
+
from dagster._core.errors import DagsterExecutionInterruptedError, DagsterPipesExecutionError
|
|
9
|
+
from dagster._core.execution.context.asset_execution_context import AssetExecutionContext
|
|
10
|
+
from dagster._core.execution.context.op_execution_context import OpExecutionContext
|
|
11
|
+
from dagster._core.pipes.client import (
|
|
12
|
+
PipesClient,
|
|
13
|
+
PipesClientCompletedInvocation,
|
|
14
|
+
PipesContextInjector,
|
|
15
|
+
PipesMessageReader,
|
|
16
|
+
)
|
|
17
|
+
from dagster._core.pipes.utils import open_pipes_session
|
|
18
|
+
from dagster_pipes import PipesExtras
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PipesAzureMLClient(PipesClient, TreatAsResourceParam):
|
|
22
|
+
"""Pipes client for Azure ML.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
client (MLClient): An Azure ML `MLClient` object.
|
|
26
|
+
context_injector (PipesContextInjector): A context injector to use to inject
|
|
27
|
+
context into the Azure ML job process.
|
|
28
|
+
message_reader (PipesMessageReader): A message reader to use to read messages
|
|
29
|
+
from the Azure ML job.
|
|
30
|
+
poll_interval_seconds (float): How long to sleep between checking the status of the job run.
|
|
31
|
+
Defaults to 5.
|
|
32
|
+
forward_termination (bool): Whether to cancel the Azure ML job if the orchestration process
|
|
33
|
+
is interrupted or canceled. Defaults to True.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
client: MLClient,
|
|
39
|
+
context_injector: PipesContextInjector,
|
|
40
|
+
message_reader: PipesMessageReader,
|
|
41
|
+
poll_interval_seconds: float = 5,
|
|
42
|
+
forward_termination: bool = True,
|
|
43
|
+
):
|
|
44
|
+
self.client = client
|
|
45
|
+
self.context_injector = check.inst_param(
|
|
46
|
+
context_injector,
|
|
47
|
+
"context_injector",
|
|
48
|
+
PipesContextInjector,
|
|
49
|
+
)
|
|
50
|
+
self.message_reader = check.inst_param(
|
|
51
|
+
message_reader,
|
|
52
|
+
"message_reader",
|
|
53
|
+
PipesMessageReader,
|
|
54
|
+
)
|
|
55
|
+
self.poll_interval_seconds = check.numeric_param(
|
|
56
|
+
poll_interval_seconds, "poll_interval_seconds"
|
|
57
|
+
)
|
|
58
|
+
self.forward_termination = check.bool_param(forward_termination, "forward_termination")
|
|
59
|
+
|
|
60
|
+
def _poll_til_success(
|
|
61
|
+
self, context: Union[OpExecutionContext, AssetExecutionContext], job_name: str
|
|
62
|
+
) -> None:
|
|
63
|
+
# poll the Azure ML job until it completes successfully, raising otherwise
|
|
64
|
+
|
|
65
|
+
last_observed_status = None
|
|
66
|
+
|
|
67
|
+
while True:
|
|
68
|
+
status = self.client.jobs.get(job_name).status
|
|
69
|
+
|
|
70
|
+
if status != last_observed_status:
|
|
71
|
+
context.log.info(
|
|
72
|
+
f"[pipes] Azure ML job {job_name} observed state transition to {status}"
|
|
73
|
+
)
|
|
74
|
+
last_observed_status = status
|
|
75
|
+
|
|
76
|
+
if status == "Completed":
|
|
77
|
+
return
|
|
78
|
+
elif status in {"Canceled", "Failed"}:
|
|
79
|
+
raise DagsterPipesExecutionError(f"Error running Azure ML job: {status}")
|
|
80
|
+
|
|
81
|
+
time.sleep(self.poll_interval_seconds)
|
|
82
|
+
|
|
83
|
+
def _poll_til_terminating(self, job_name: str) -> None:
|
|
84
|
+
while True:
|
|
85
|
+
status = self.client.jobs.get(job_name).status
|
|
86
|
+
if status in {"Completed", "Canceled", "Failed"}:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
time.sleep(self.poll_interval_seconds)
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def _is_dagster_maintained(cls) -> bool:
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
def run( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
96
|
+
self,
|
|
97
|
+
*,
|
|
98
|
+
context: Union[OpExecutionContext, AssetExecutionContext],
|
|
99
|
+
extras: Optional[PipesExtras] = None,
|
|
100
|
+
command: Command,
|
|
101
|
+
) -> PipesClientCompletedInvocation:
|
|
102
|
+
"""Synchronously execute an Azure ML job with the pipes protocol.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
command (azure.ai.ml.entities.Command): Specification of the Azure ML
|
|
106
|
+
command job to run. Pipes bootstrap parameters will be passed via environment
|
|
107
|
+
variables which will be merged with any existing environment variables in the command.
|
|
108
|
+
The Azure ML job code should use :py:func:`dagster_pipes.open_dagster_pipes` to
|
|
109
|
+
initialize the Pipes connection.
|
|
110
|
+
context (Union[OpExecutionContext, AssetExecutionContext]): The context from the executing op or asset.
|
|
111
|
+
extras (Optional[PipesExtras]): An optional dict of extra parameters to pass to the
|
|
112
|
+
Azure ML job.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
PipesClientCompletedInvocation: Wrapper containing results reported by the external
|
|
116
|
+
process.
|
|
117
|
+
"""
|
|
118
|
+
with open_pipes_session(
|
|
119
|
+
context=context,
|
|
120
|
+
extras=extras,
|
|
121
|
+
context_injector=self.context_injector,
|
|
122
|
+
message_reader=self.message_reader,
|
|
123
|
+
) as pipes_session:
|
|
124
|
+
command.environment_variables = {
|
|
125
|
+
**command.environment_variables,
|
|
126
|
+
**pipes_session.get_bootstrap_env_vars(),
|
|
127
|
+
}
|
|
128
|
+
job = self.client.create_or_update(command)
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
self._poll_til_success(context, job.name) # pyright: ignore[reportArgumentType]
|
|
132
|
+
except DagsterExecutionInterruptedError:
|
|
133
|
+
if self.forward_termination:
|
|
134
|
+
context.log.info("[pipes] execution interrupted, canceling Azure ML job.")
|
|
135
|
+
self.client.jobs.begin_cancel(job.name) # pyright: ignore[reportArgumentType]
|
|
136
|
+
self._poll_til_terminating(job.name) # pyright: ignore[reportArgumentType]
|
|
137
|
+
|
|
138
|
+
return PipesClientCompletedInvocation(
|
|
139
|
+
pipes_session, metadata={"AzureML Job Name": job.name}
|
|
140
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import random
|
|
4
|
+
import string
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
|
|
8
|
+
import dagster._check as check
|
|
9
|
+
from azure.storage.blob import BlobServiceClient
|
|
10
|
+
from dagster._core.pipes.client import PipesContextInjector, PipesParams
|
|
11
|
+
from dagster_pipes import PipesContextData
|
|
12
|
+
|
|
13
|
+
_CONTEXT_FILENAME = "context.json"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PipesAzureBlobStorageContextInjector(PipesContextInjector):
|
|
17
|
+
"""A context injector that injects context by writing to a temporary AzureBlobStorage location.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
container (str): The AzureBlobStorage container to write to.
|
|
21
|
+
client (azure.storage.blob.BlobServiceClient): An Azure Blob Storage client.
|
|
22
|
+
key_prefix (Optional[str]): An optional prefix to use for the Azure Blob Storage key. Defaults to a random
|
|
23
|
+
string.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, *, container: str, client: BlobServiceClient):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.bucket = check.str_param(container, "container")
|
|
30
|
+
self.client = client
|
|
31
|
+
|
|
32
|
+
@contextmanager
|
|
33
|
+
def inject_context(self, context: PipesContextData) -> Iterator[PipesParams]: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
34
|
+
key_prefix = "".join(random.choices(string.ascii_letters, k=30))
|
|
35
|
+
key = os.path.join(key_prefix, _CONTEXT_FILENAME)
|
|
36
|
+
|
|
37
|
+
with self.client.get_blob_client(self.bucket, key) as blob_client:
|
|
38
|
+
blob_client.upload_blob(json.dumps(context).encode("utf-8"))
|
|
39
|
+
yield {"bucket": self.bucket, "key": key}
|
|
40
|
+
blob_client.delete_blob()
|
|
41
|
+
|
|
42
|
+
def no_messages_debug_text(self) -> str:
|
|
43
|
+
return (
|
|
44
|
+
"Attempted to inject context via a temporary file in AzureBlobStorage. Expected"
|
|
45
|
+
" PipesAzureBlobStorageContextLoader to be explicitly passed to open_dagster_pipes in the external"
|
|
46
|
+
" process."
|
|
47
|
+
)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import string
|
|
3
|
+
from collections.abc import Iterator, Sequence
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import dagster._check as check
|
|
8
|
+
from azure.storage.blob import BlobServiceClient
|
|
9
|
+
from dagster._core.pipes.client import PipesParams
|
|
10
|
+
from dagster._core.pipes.utils import PipesBlobStoreMessageReader, PipesLogReader
|
|
11
|
+
from dagster_pipes import PipesBlobStoreMessageWriter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PipesAzureBlobStorageMessageReader(PipesBlobStoreMessageReader):
|
|
15
|
+
"""Message reader that reads messages by periodically reading message chunks from a specified AzureBlobStorage
|
|
16
|
+
container.
|
|
17
|
+
|
|
18
|
+
If `log_readers` is passed, this reader will also start the passed readers
|
|
19
|
+
when the first message is received from the external process.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
interval (float): interval in seconds between attempts to download a chunk
|
|
23
|
+
container (str): The AzureBlobStorage container to read from.
|
|
24
|
+
client (azure.storage.blob.BlobServiceClient): An azure BlobServiceClient.
|
|
25
|
+
log_readers (Optional[Sequence[PipesLogReader]]): A set of log readers for logs on AzureBlobStorage.
|
|
26
|
+
include_stdio_in_messages (bool): Whether to send stdout/stderr to Dagster via Pipes messages. Defaults to False.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
interval: float = 10,
|
|
33
|
+
container: str,
|
|
34
|
+
client: BlobServiceClient,
|
|
35
|
+
log_readers: Optional[Sequence[PipesLogReader]] = None,
|
|
36
|
+
include_stdio_in_messages: bool = False,
|
|
37
|
+
):
|
|
38
|
+
super().__init__(
|
|
39
|
+
interval=interval,
|
|
40
|
+
log_readers=log_readers,
|
|
41
|
+
)
|
|
42
|
+
self.bucket = check.str_param(container, "container")
|
|
43
|
+
self.include_stdio_in_messages = check.bool_param(
|
|
44
|
+
include_stdio_in_messages, "include_stdio_in_messages"
|
|
45
|
+
)
|
|
46
|
+
self.client = client
|
|
47
|
+
|
|
48
|
+
@contextmanager
|
|
49
|
+
def get_params(self) -> Iterator[PipesParams]:
|
|
50
|
+
key_prefix = "".join(random.choices(string.ascii_letters, k=30))
|
|
51
|
+
yield {
|
|
52
|
+
"bucket": self.bucket,
|
|
53
|
+
"key_prefix": key_prefix,
|
|
54
|
+
PipesBlobStoreMessageWriter.INCLUDE_STDIO_IN_MESSAGES_KEY: self.include_stdio_in_messages,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def messages_are_readable(self, params: PipesParams) -> bool:
|
|
58
|
+
key_prefix = params.get("key_prefix")
|
|
59
|
+
if key_prefix is not None:
|
|
60
|
+
try:
|
|
61
|
+
with self.client.get_blob_client(
|
|
62
|
+
self.bucket, f"{key_prefix}/1.json"
|
|
63
|
+
) as blob_client:
|
|
64
|
+
return blob_client.exists()
|
|
65
|
+
except Exception:
|
|
66
|
+
return False
|
|
67
|
+
else:
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def download_messages_chunk(self, index: int, params: PipesParams) -> Optional[str]:
|
|
71
|
+
key = f"{params['key_prefix']}/{index}.json"
|
|
72
|
+
try:
|
|
73
|
+
with self.client.get_blob_client(self.bucket, key) as blob_client:
|
|
74
|
+
return blob_client.download_blob().readall().decode("utf-8")
|
|
75
|
+
except Exception:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
def no_messages_debug_text(self) -> str:
|
|
79
|
+
return (
|
|
80
|
+
f"Attempted to read messages from Azure Blob Storage container "
|
|
81
|
+
f"{self.bucket}. Expected PipesAzureBlobStorageMessageWriter to be "
|
|
82
|
+
"explicitly passed to open_dagster_pipes in the external process."
|
|
83
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.28.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dagster-azure
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.28.0
|
|
4
4
|
Summary: Package for Azure-specific Dagster framework op and resource components.
|
|
5
5
|
Home-page: https://github.com/dagster-io/dagster/tree/master/python_modules/libraries/dagster-azure
|
|
6
6
|
Author: Dagster Labs
|
|
@@ -17,9 +17,10 @@ Requires-Python: >=3.9,<3.14
|
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: azure-core<2.0.0,>=1.7.0
|
|
19
19
|
Requires-Dist: azure-identity<2.0.0,>=1.7.0
|
|
20
|
+
Requires-Dist: azure-ai-ml<2.0.0,>=1.28.0
|
|
20
21
|
Requires-Dist: azure-storage-blob<13.0.0,>=12.5.0
|
|
21
22
|
Requires-Dist: azure-storage-file-datalake<13.0.0,>=12.5
|
|
22
|
-
Requires-Dist: dagster==1.
|
|
23
|
+
Requires-Dist: dagster==1.12.0
|
|
23
24
|
Dynamic: author
|
|
24
25
|
Dynamic: author-email
|
|
25
26
|
Dynamic: classifier
|
|
@@ -24,4 +24,9 @@ dagster_azure/blob/fake_blob_client.py
|
|
|
24
24
|
dagster_azure/blob/resources.py
|
|
25
25
|
dagster_azure/blob/utils.py
|
|
26
26
|
dagster_azure/fakes/__init__.py
|
|
27
|
-
dagster_azure/fakes/fake_adls2_resource.py
|
|
27
|
+
dagster_azure/fakes/fake_adls2_resource.py
|
|
28
|
+
dagster_azure/pipes/__init__.py
|
|
29
|
+
dagster_azure/pipes/context_injectors.py
|
|
30
|
+
dagster_azure/pipes/message_readers.py
|
|
31
|
+
dagster_azure/pipes/clients/__init__.py
|
|
32
|
+
dagster_azure/pipes/clients/azureml.py
|
|
@@ -37,9 +37,10 @@ setup(
|
|
|
37
37
|
install_requires=[
|
|
38
38
|
"azure-core<2.0.0,>=1.7.0",
|
|
39
39
|
"azure-identity<2.0.0,>=1.7.0",
|
|
40
|
+
"azure-ai-ml<2.0.0,>=1.28.0",
|
|
40
41
|
"azure-storage-blob<13.0.0,>=12.5.0",
|
|
41
42
|
"azure-storage-file-datalake<13.0.0,>=12.5",
|
|
42
|
-
"dagster==1.
|
|
43
|
+
"dagster==1.12.0",
|
|
43
44
|
],
|
|
44
45
|
entry_points={"console_scripts": ["dagster-azure = dagster_azure.cli.cli:main"]},
|
|
45
46
|
zip_safe=False,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.27.15"
|
|
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
|