modal 1.0.3.dev22__py3-none-any.whl → 1.0.3.dev25__py3-none-any.whl
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.
- modal/client.pyi +2 -2
- modal/functions.pyi +6 -6
- modal/mount.py +6 -2
- modal/mount.pyi +9 -3
- modal/parallel_map.py +46 -22
- {modal-1.0.3.dev22.dist-info → modal-1.0.3.dev25.dist-info}/METADATA +1 -1
- {modal-1.0.3.dev22.dist-info → modal-1.0.3.dev25.dist-info}/RECORD +12 -12
- modal_version/__init__.py +1 -1
- {modal-1.0.3.dev22.dist-info → modal-1.0.3.dev25.dist-info}/WHEEL +0 -0
- {modal-1.0.3.dev22.dist-info → modal-1.0.3.dev25.dist-info}/entry_points.txt +0 -0
- {modal-1.0.3.dev22.dist-info → modal-1.0.3.dev25.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.3.dev22.dist-info → modal-1.0.3.dev25.dist-info}/top_level.txt +0 -0
modal/client.pyi
CHANGED
@@ -31,7 +31,7 @@ class _Client:
|
|
31
31
|
server_url: str,
|
32
32
|
client_type: int,
|
33
33
|
credentials: typing.Optional[tuple[str, str]],
|
34
|
-
version: str = "1.0.3.
|
34
|
+
version: str = "1.0.3.dev25",
|
35
35
|
): ...
|
36
36
|
def is_closed(self) -> bool: ...
|
37
37
|
@property
|
@@ -94,7 +94,7 @@ class Client:
|
|
94
94
|
server_url: str,
|
95
95
|
client_type: int,
|
96
96
|
credentials: typing.Optional[tuple[str, str]],
|
97
|
-
version: str = "1.0.3.
|
97
|
+
version: str = "1.0.3.dev25",
|
98
98
|
): ...
|
99
99
|
def is_closed(self) -> bool: ...
|
100
100
|
@property
|
modal/functions.pyi
CHANGED
@@ -227,11 +227,11 @@ class Function(
|
|
227
227
|
|
228
228
|
_call_generator: ___call_generator_spec[typing_extensions.Self]
|
229
229
|
|
230
|
-
class __remote_spec(typing_extensions.Protocol[
|
230
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
231
231
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
232
232
|
async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
233
233
|
|
234
|
-
remote: __remote_spec[modal._functions.
|
234
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
235
235
|
|
236
236
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
237
237
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
@@ -246,12 +246,12 @@ class Function(
|
|
246
246
|
self, *args: modal._functions.P.args, **kwargs: modal._functions.P.kwargs
|
247
247
|
) -> modal._functions.OriginalReturnType: ...
|
248
248
|
|
249
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
249
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
250
250
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
251
251
|
async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
252
252
|
|
253
253
|
_experimental_spawn: ___experimental_spawn_spec[
|
254
|
-
modal._functions.
|
254
|
+
modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
|
255
255
|
]
|
256
256
|
|
257
257
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
|
@@ -260,11 +260,11 @@ class Function(
|
|
260
260
|
|
261
261
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
|
262
262
|
|
263
|
-
class __spawn_spec(typing_extensions.Protocol[
|
263
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
264
264
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
265
265
|
async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
266
266
|
|
267
|
-
spawn: __spawn_spec[modal._functions.
|
267
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
268
268
|
|
269
269
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]: ...
|
270
270
|
|
modal/mount.py
CHANGED
@@ -900,9 +900,13 @@ async def _create_single_mount(
|
|
900
900
|
print(f"✅ Deployed mount {mount_name} to global namespace.")
|
901
901
|
|
902
902
|
|
903
|
-
async def _create_client_dependency_mounts(
|
903
|
+
async def _create_client_dependency_mounts(
|
904
|
+
client=None,
|
905
|
+
check_if_exists=True,
|
906
|
+
python_versions: list[str] = list(PYTHON_STANDALONE_VERSIONS),
|
907
|
+
):
|
904
908
|
coros = []
|
905
|
-
for python_version in
|
909
|
+
for python_version in python_versions:
|
906
910
|
# glibc >= 2.17
|
907
911
|
coros.append(
|
908
912
|
_create_single_mount(
|
modal/mount.pyi
CHANGED
@@ -317,11 +317,17 @@ async def _create_single_mount(
|
|
317
317
|
uv_python_platform: str = None,
|
318
318
|
check_if_exists: bool = True,
|
319
319
|
): ...
|
320
|
-
async def _create_client_dependency_mounts(
|
320
|
+
async def _create_client_dependency_mounts(
|
321
|
+
client=None, check_if_exists=True, python_versions: list[str] = ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
322
|
+
): ...
|
321
323
|
|
322
324
|
class __create_client_dependency_mounts_spec(typing_extensions.Protocol):
|
323
|
-
def __call__(
|
324
|
-
|
325
|
+
def __call__(
|
326
|
+
self, /, client=None, check_if_exists=True, python_versions: list[str] = ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
327
|
+
): ...
|
328
|
+
async def aio(
|
329
|
+
self, /, client=None, check_if_exists=True, python_versions: list[str] = ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
330
|
+
): ...
|
325
331
|
|
326
332
|
create_client_dependency_mounts: __create_client_dependency_mounts_spec
|
327
333
|
|
modal/parallel_map.py
CHANGED
@@ -3,6 +3,7 @@ import asyncio
|
|
3
3
|
import enum
|
4
4
|
import time
|
5
5
|
import typing
|
6
|
+
from asyncio import FIRST_COMPLETED
|
6
7
|
from dataclasses import dataclass
|
7
8
|
from typing import Any, Callable, Optional
|
8
9
|
|
@@ -110,6 +111,7 @@ async def _map_invocation(
|
|
110
111
|
max_inputs_outstanding = response.max_inputs_outstanding or MAX_INPUTS_OUTSTANDING_DEFAULT
|
111
112
|
|
112
113
|
have_all_inputs = False
|
114
|
+
map_done_event = asyncio.Event()
|
113
115
|
inputs_created = 0
|
114
116
|
inputs_sent = 0
|
115
117
|
inputs_retried = 0
|
@@ -122,10 +124,6 @@ async def _map_invocation(
|
|
122
124
|
stale_retry_duplicates = 0
|
123
125
|
no_context_duplicates = 0
|
124
126
|
|
125
|
-
def count_update():
|
126
|
-
if count_update_callback is not None:
|
127
|
-
count_update_callback(outputs_completed, inputs_created)
|
128
|
-
|
129
127
|
retry_queue = TimestampPriorityQueue()
|
130
128
|
completed_outputs: set[str] = set() # Set of input_ids whose outputs are complete (expecting no more values)
|
131
129
|
input_queue: asyncio.Queue[api_pb2.FunctionPutInputsItem | None] = asyncio.Queue()
|
@@ -134,9 +132,8 @@ async def _map_invocation(
|
|
134
132
|
)
|
135
133
|
|
136
134
|
async def create_input(argskwargs):
|
137
|
-
nonlocal inputs_created
|
138
135
|
idx = inputs_created
|
139
|
-
inputs_created
|
136
|
+
update_state(set_inputs_created=inputs_created + 1)
|
140
137
|
(args, kwargs) = argskwargs
|
141
138
|
return await _create_input(args, kwargs, client.stub, idx=idx, method_name=function._use_method_name)
|
142
139
|
|
@@ -147,9 +144,27 @@ async def _map_invocation(
|
|
147
144
|
break
|
148
145
|
yield raw_input # args, kwargs
|
149
146
|
|
150
|
-
|
151
|
-
nonlocal
|
147
|
+
def update_state(set_have_all_inputs=None, set_inputs_created=None, set_outputs_completed=None):
|
148
|
+
# This should be the only method that needs nonlocal of the following vars
|
149
|
+
nonlocal have_all_inputs, inputs_created, outputs_completed
|
150
|
+
assert set_have_all_inputs is not False # not allowed
|
151
|
+
assert set_inputs_created is None or set_inputs_created > inputs_created
|
152
|
+
assert set_outputs_completed is None or set_outputs_completed > outputs_completed
|
153
|
+
if set_have_all_inputs is not None:
|
154
|
+
have_all_inputs = set_have_all_inputs
|
155
|
+
if set_inputs_created is not None:
|
156
|
+
inputs_created = set_inputs_created
|
157
|
+
if set_outputs_completed is not None:
|
158
|
+
outputs_completed = set_outputs_completed
|
159
|
+
|
160
|
+
if count_update_callback is not None:
|
161
|
+
count_update_callback(outputs_completed, inputs_created)
|
162
|
+
|
163
|
+
if have_all_inputs and outputs_completed >= inputs_created:
|
164
|
+
# map is done
|
165
|
+
map_done_event.set()
|
152
166
|
|
167
|
+
async def drain_input_generator():
|
153
168
|
# Parallelize uploading blobs
|
154
169
|
async with aclosing(
|
155
170
|
async_map_ordered(input_iter(), create_input, concurrency=BLOB_MAX_PARALLELISM)
|
@@ -159,12 +174,12 @@ async def _map_invocation(
|
|
159
174
|
|
160
175
|
# close queue iterator
|
161
176
|
await input_queue.put(None)
|
162
|
-
|
177
|
+
update_state(set_have_all_inputs=True)
|
163
178
|
yield
|
164
179
|
|
165
180
|
async def pump_inputs():
|
166
181
|
assert client.stub
|
167
|
-
nonlocal
|
182
|
+
nonlocal inputs_sent
|
168
183
|
async for items in queue_batch_iterator(input_queue, max_batch_size=MAP_INVOCATION_CHUNK_SIZE):
|
169
184
|
# Add items to the manager. Their state will be SENDING.
|
170
185
|
await map_items_manager.add_items(items)
|
@@ -178,7 +193,6 @@ async def _map_invocation(
|
|
178
193
|
)
|
179
194
|
|
180
195
|
resp = await send_inputs(client.stub.FunctionPutInputs, request)
|
181
|
-
count_update()
|
182
196
|
inputs_sent += len(items)
|
183
197
|
# Change item state to WAITING_FOR_OUTPUT, and set the input_id and input_jwt which are in the response.
|
184
198
|
map_items_manager.handle_put_inputs_response(resp.inputs)
|
@@ -231,11 +245,8 @@ async def _map_invocation(
|
|
231
245
|
async def get_all_outputs():
|
232
246
|
assert client.stub
|
233
247
|
nonlocal \
|
234
|
-
inputs_created, \
|
235
248
|
successful_completions, \
|
236
249
|
failed_completions, \
|
237
|
-
outputs_completed, \
|
238
|
-
have_all_inputs, \
|
239
250
|
outputs_received, \
|
240
251
|
already_complete_duplicates, \
|
241
252
|
no_context_duplicates, \
|
@@ -244,7 +255,7 @@ async def _map_invocation(
|
|
244
255
|
|
245
256
|
last_entry_id = "0-0"
|
246
257
|
|
247
|
-
while not
|
258
|
+
while not map_done_event.is_set():
|
248
259
|
logger.debug(f"Requesting outputs. Have {outputs_completed} outputs, {inputs_created} inputs.")
|
249
260
|
# Get input_jwts of all items in the WAITING_FOR_OUTPUT state.
|
250
261
|
# The server uses these to track for lost inputs.
|
@@ -258,12 +269,26 @@ async def _map_invocation(
|
|
258
269
|
requested_at=time.time(),
|
259
270
|
input_jwts=input_jwts,
|
260
271
|
)
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
272
|
+
get_response_task = asyncio.create_task(
|
273
|
+
retry_transient_errors(
|
274
|
+
client.stub.FunctionGetOutputs,
|
275
|
+
request,
|
276
|
+
max_retries=20,
|
277
|
+
attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
278
|
+
)
|
266
279
|
)
|
280
|
+
map_done_task = asyncio.create_task(map_done_event.wait())
|
281
|
+
done, pending = await asyncio.wait([get_response_task, map_done_task], return_when=FIRST_COMPLETED)
|
282
|
+
if get_response_task in done:
|
283
|
+
map_done_task.cancel()
|
284
|
+
response = get_response_task.result()
|
285
|
+
else:
|
286
|
+
assert map_done_event.is_set()
|
287
|
+
# map is done, cancel the pending call
|
288
|
+
get_response_task.cancel()
|
289
|
+
# not strictly necessary - don't leave dangling task
|
290
|
+
await asyncio.gather(get_response_task, return_exceptions=True)
|
291
|
+
return
|
267
292
|
|
268
293
|
last_entry_id = response.last_entry_id
|
269
294
|
now_seconds = int(time.time())
|
@@ -288,7 +313,7 @@ async def _map_invocation(
|
|
288
313
|
|
289
314
|
if output_type == _OutputType.SUCCESSFUL_COMPLETION or output_type == _OutputType.FAILED_COMPLETION:
|
290
315
|
completed_outputs.add(item.input_id)
|
291
|
-
outputs_completed
|
316
|
+
update_state(set_outputs_completed=outputs_completed + 1)
|
292
317
|
yield item
|
293
318
|
|
294
319
|
async def get_all_outputs_and_clean_up():
|
@@ -328,7 +353,6 @@ async def _map_invocation(
|
|
328
353
|
async_map_ordered(get_all_outputs_and_clean_up(), fetch_output, concurrency=BLOB_MAX_PARALLELISM)
|
329
354
|
) as streamer:
|
330
355
|
async for idx, output in streamer:
|
331
|
-
count_update()
|
332
356
|
if not order_outputs:
|
333
357
|
yield _OutputValue(output)
|
334
358
|
else:
|
@@ -22,7 +22,7 @@ modal/app.py,sha256=NZ_rJ9TuMfiNiLg8-gOFgufD5flGtXWPHOZI0gdD3hE,46585
|
|
22
22
|
modal/app.pyi,sha256=4-b_vbe3lNAqQPcMRpQCEDsE1zsVkQRJGUql9B7HvbM,22659
|
23
23
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
24
24
|
modal/client.py,sha256=OwISJvkgMb-rHm9Gc4i-7YcDgGiZgwJ7F_PzwZH7a6Q,16847
|
25
|
-
modal/client.pyi,sha256
|
25
|
+
modal/client.pyi,sha256=-0UZ9iSoym_4ee51dTSAcuyg11LMgLCfR43yto7trxM,8459
|
26
26
|
modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
|
27
27
|
modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
|
28
28
|
modal/cls.py,sha256=dBbeARwOWftlKd1cwtM0cHFtQWSWkwVXwVmOV4w0SyI,37907
|
@@ -39,20 +39,20 @@ modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
|
|
39
39
|
modal/file_io.pyi,sha256=oB7x-rKq7bmm8cA7Z7W9C9yeko7KK9m9i5GidFnkGK4,9569
|
40
40
|
modal/file_pattern_matcher.py,sha256=wov-otB5M1oTdrYDtR2_VgacYin2srdtAP4McA1Cqzw,6516
|
41
41
|
modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
|
42
|
-
modal/functions.pyi,sha256=
|
42
|
+
modal/functions.pyi,sha256=5T58OucdNU4I-LqhBdwsWSAGka-Wa8nP2GcZ5K1bOL0,16236
|
43
43
|
modal/gpu.py,sha256=Kbhs_u49FaC2Zi0TjCdrpstpRtT5eZgecynmQi5IZVE,6752
|
44
44
|
modal/image.py,sha256=yrI9DCw7GAck3d788GCHJom-_yU55zNu7reNapBhlgE,93284
|
45
45
|
modal/image.pyi,sha256=2xjB6XOZDtm_chDdd90UoIj8pnDt5hCg6bOmu5fNaA4,25625
|
46
46
|
modal/io_streams.py,sha256=YDZVQSDv05DeXg5TwcucC9Rj5hQBx2GXdluan9rIUpw,15467
|
47
47
|
modal/io_streams.pyi,sha256=1UK6kWLREASQfq-wL9wSp5iqjLU0egRZPDn4LXs1PZY,5136
|
48
|
-
modal/mount.py,sha256=
|
49
|
-
modal/mount.pyi,sha256=
|
48
|
+
modal/mount.py,sha256=HbSN-45SWLB7MvIeGcBDKa5nP0Z3wFyKIRX-B37Epw4,34637
|
49
|
+
modal/mount.pyi,sha256=h4yzzppRKvD3JuTTjm9hE2Ye_aUKueTV09F5I_Regjg,13364
|
50
50
|
modal/network_file_system.py,sha256=lgtmHYjzA5gDMx0tysH0-WJB2Ao9JD2W15NyYK2A7_w,14612
|
51
51
|
modal/network_file_system.pyi,sha256=58DiUqHGlARmI3cz-Yo7IFObKKFIiGh5UIU5JxGNFfc,8333
|
52
52
|
modal/object.py,sha256=bTeskuY8JFrESjU4_UL_nTwYlBQdOLmVaOX3X6EMxsg,164
|
53
53
|
modal/object.pyi,sha256=UkR8NQ1jCIaw3hBUPxBRc6vvrOqtV37G_hsW2O5-4wE,5378
|
54
54
|
modal/output.py,sha256=q4T9uHduunj4NwY-YSwkHGgjZlCXMuJbfQ5UFaAGRAc,1968
|
55
|
-
modal/parallel_map.py,sha256=
|
55
|
+
modal/parallel_map.py,sha256=uX_n0FXrph4D2_2prjqTJCua6TZ4the3BBcsxS5zM-E,37418
|
56
56
|
modal/parallel_map.pyi,sha256=mhYGQmufQEJbjNrX7vNhBS2gUdfBrpmuWNUHth_Dz6U,6140
|
57
57
|
modal/partial_function.py,sha256=SwuAAj2wj4SO6F6nkSnwNZrczEmm9w9YdlQTHh6hr04,1195
|
58
58
|
modal/partial_function.pyi,sha256=NFWz1aCAs2B3-GnPf1cTatWRZOLnYpFKCnjP_X9iNRs,6411
|
@@ -147,7 +147,7 @@ modal/requirements/2024.10.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddR
|
|
147
147
|
modal/requirements/PREVIEW.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddRo,296
|
148
148
|
modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
|
149
149
|
modal/requirements/base-images.json,sha256=57vMSqzMbLBxw5tFWSaMiIkkVEps4JfX5PAtXGnkS4U,740
|
150
|
-
modal-1.0.3.
|
150
|
+
modal-1.0.3.dev25.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
151
151
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
152
152
|
modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
|
153
153
|
modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
|
@@ -170,10 +170,10 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
|
|
170
170
|
modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
171
171
|
modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
|
172
172
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
173
|
-
modal_version/__init__.py,sha256=
|
173
|
+
modal_version/__init__.py,sha256=qYYgW63q2xAam-da5EC6uPWCwf57DR0VMWQoQXY8zkk,121
|
174
174
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
175
|
-
modal-1.0.3.
|
176
|
-
modal-1.0.3.
|
177
|
-
modal-1.0.3.
|
178
|
-
modal-1.0.3.
|
179
|
-
modal-1.0.3.
|
175
|
+
modal-1.0.3.dev25.dist-info/METADATA,sha256=FBQJ2jHfOZyVsAuRsTzljPnml6vIjMPl1zDV7A-bu8M,2455
|
176
|
+
modal-1.0.3.dev25.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
177
|
+
modal-1.0.3.dev25.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
178
|
+
modal-1.0.3.dev25.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
179
|
+
modal-1.0.3.dev25.dist-info/RECORD,,
|
modal_version/__init__.py
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|