ingestr 0.13.52__py3-none-any.whl → 0.13.53__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.
Potentially problematic release.
This version of ingestr might be problematic. Click here for more details.
- ingestr/src/buildinfo.py +1 -1
- ingestr/src/destinations.py +18 -0
- ingestr/src/factory.py +6 -0
- ingestr/src/sources.py +55 -25
- {ingestr-0.13.52.dist-info → ingestr-0.13.53.dist-info}/METADATA +51 -4
- {ingestr-0.13.52.dist-info → ingestr-0.13.53.dist-info}/RECORD +9 -9
- {ingestr-0.13.52.dist-info → ingestr-0.13.53.dist-info}/WHEEL +0 -0
- {ingestr-0.13.52.dist-info → ingestr-0.13.53.dist-info}/entry_points.txt +0 -0
- {ingestr-0.13.52.dist-info → ingestr-0.13.53.dist-info}/licenses/LICENSE.md +0 -0
ingestr/src/buildinfo.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = "v0.13.
|
|
1
|
+
version = "v0.13.53"
|
ingestr/src/destinations.py
CHANGED
|
@@ -480,3 +480,21 @@ class SqliteDestination(GenericSqlDestination):
|
|
|
480
480
|
"dataset_name": "main",
|
|
481
481
|
"table_name": table,
|
|
482
482
|
}
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class MySqlDestination(GenericSqlDestination):
|
|
486
|
+
def dlt_dest(self, uri: str, **kwargs):
|
|
487
|
+
return dlt.destinations.sqlalchemy(credentials=uri)
|
|
488
|
+
|
|
489
|
+
def dlt_run_params(self, uri: str, table: str, **kwargs):
|
|
490
|
+
parsed = urlparse(uri)
|
|
491
|
+
database = parsed.path.lstrip("/")
|
|
492
|
+
if not database:
|
|
493
|
+
raise ValueError("You need to specify a database")
|
|
494
|
+
return {
|
|
495
|
+
"dataset_name": database,
|
|
496
|
+
"table_name": table,
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
|
ingestr/src/factory.py
CHANGED
|
@@ -11,6 +11,7 @@ from ingestr.src.destinations import (
|
|
|
11
11
|
DatabricksDestination,
|
|
12
12
|
DuckDBDestination,
|
|
13
13
|
MsSQLDestination,
|
|
14
|
+
MySqlDestination,
|
|
14
15
|
PostgresDestination,
|
|
15
16
|
RedshiftDestination,
|
|
16
17
|
S3Destination,
|
|
@@ -53,6 +54,7 @@ from ingestr.src.sources import (
|
|
|
53
54
|
PipedriveSource,
|
|
54
55
|
S3Source,
|
|
55
56
|
SalesforceSource,
|
|
57
|
+
SFTPSource,
|
|
56
58
|
ShopifySource,
|
|
57
59
|
SlackSource,
|
|
58
60
|
SmartsheetSource,
|
|
@@ -65,6 +67,7 @@ from ingestr.src.sources import (
|
|
|
65
67
|
|
|
66
68
|
SQL_SOURCE_SCHEMES = [
|
|
67
69
|
"bigquery",
|
|
70
|
+
"crate",
|
|
68
71
|
"duckdb",
|
|
69
72
|
"mssql",
|
|
70
73
|
"mysql",
|
|
@@ -164,6 +167,7 @@ class SourceDestinationFactory:
|
|
|
164
167
|
"attio": AttioSource,
|
|
165
168
|
"solidgate": SolidgateSource,
|
|
166
169
|
"smartsheet": SmartsheetSource,
|
|
170
|
+
"sftp": SFTPSource,
|
|
167
171
|
}
|
|
168
172
|
destinations: Dict[str, Type[DestinationProtocol]] = {
|
|
169
173
|
"bigquery": BigQueryDestination,
|
|
@@ -184,6 +188,8 @@ class SourceDestinationFactory:
|
|
|
184
188
|
"clickhouse": ClickhouseDestination,
|
|
185
189
|
"s3": S3Destination,
|
|
186
190
|
"sqlite": SqliteDestination,
|
|
191
|
+
"mysql": MySqlDestination,
|
|
192
|
+
"mysql+pymysql": MySqlDestination,
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
def __init__(self, source_uri: str, destination_uri: str):
|
ingestr/src/sources.py
CHANGED
|
@@ -18,6 +18,7 @@ from typing import (
|
|
|
18
18
|
)
|
|
19
19
|
from urllib.parse import ParseResult, parse_qs, quote, urlencode, urlparse
|
|
20
20
|
|
|
21
|
+
import fsspec # type: ignore
|
|
21
22
|
import pendulum
|
|
22
23
|
from dlt.common.time import ensure_pendulum_datetime
|
|
23
24
|
from dlt.extract import Incremental
|
|
@@ -78,31 +79,7 @@ class SqlSource:
|
|
|
78
79
|
# clickhouse://<username>:<password>@<host>:<port>?secure=<secure>
|
|
79
80
|
if uri.startswith("clickhouse://"):
|
|
80
81
|
parsed_uri = urlparse(uri)
|
|
81
|
-
|
|
82
|
-
username = parsed_uri.username
|
|
83
|
-
if not username:
|
|
84
|
-
raise ValueError(
|
|
85
|
-
"A username is required to connect to the ClickHouse database."
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
password = parsed_uri.password
|
|
89
|
-
if not password:
|
|
90
|
-
raise ValueError(
|
|
91
|
-
"A password is required to authenticate with the ClickHouse database."
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
host = parsed_uri.hostname
|
|
95
|
-
if not host:
|
|
96
|
-
raise ValueError(
|
|
97
|
-
"The hostname or IP address of the ClickHouse server is required to establish a connection."
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
port = parsed_uri.port
|
|
101
|
-
if not port:
|
|
102
|
-
raise ValueError(
|
|
103
|
-
"The TCP port of the ClickHouse server is required to establish a connection."
|
|
104
|
-
)
|
|
105
|
-
|
|
82
|
+
|
|
106
83
|
query_params = parse_qs(parsed_uri.query)
|
|
107
84
|
|
|
108
85
|
if "http_port" in query_params:
|
|
@@ -2506,3 +2483,56 @@ class SolidgateSource:
|
|
|
2506
2483
|
).with_resources(table_name)
|
|
2507
2484
|
except ResourcesNotFoundError:
|
|
2508
2485
|
raise UnsupportedResourceError(table_name, "Solidgate")
|
|
2486
|
+
|
|
2487
|
+
|
|
2488
|
+
class SFTPSource:
|
|
2489
|
+
def handles_incrementality(self) -> bool:
|
|
2490
|
+
return True
|
|
2491
|
+
|
|
2492
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
2493
|
+
parsed_uri = urlparse(uri)
|
|
2494
|
+
host = parsed_uri.hostname
|
|
2495
|
+
if not host:
|
|
2496
|
+
raise MissingValueError("host", "SFTP URI")
|
|
2497
|
+
port = parsed_uri.port or 22
|
|
2498
|
+
username = parsed_uri.username
|
|
2499
|
+
password = parsed_uri.password
|
|
2500
|
+
|
|
2501
|
+
params: Dict[str, Any] = {
|
|
2502
|
+
"host": host,
|
|
2503
|
+
"port": port,
|
|
2504
|
+
"username": username,
|
|
2505
|
+
"password": password,
|
|
2506
|
+
"look_for_keys": False,
|
|
2507
|
+
"allow_agent": False,
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
try:
|
|
2511
|
+
fs = fsspec.filesystem("sftp", **params)
|
|
2512
|
+
except Exception as e:
|
|
2513
|
+
raise ConnectionError(
|
|
2514
|
+
f"Failed to connect or authenticate to sftp server {host}:{port}. Error: {e}"
|
|
2515
|
+
)
|
|
2516
|
+
bucket_url = f"sftp://{host}:{port}"
|
|
2517
|
+
|
|
2518
|
+
if table.startswith("/"):
|
|
2519
|
+
file_glob = table
|
|
2520
|
+
else:
|
|
2521
|
+
file_glob = f"/{table}"
|
|
2522
|
+
|
|
2523
|
+
file_extension = table.split(".")[-1].lower()
|
|
2524
|
+
endpoint: str
|
|
2525
|
+
if file_extension == "csv":
|
|
2526
|
+
endpoint = "read_csv"
|
|
2527
|
+
elif file_extension == "jsonl":
|
|
2528
|
+
endpoint = "read_jsonl"
|
|
2529
|
+
elif file_extension == "parquet":
|
|
2530
|
+
endpoint = "read_parquet"
|
|
2531
|
+
else:
|
|
2532
|
+
raise ValueError(
|
|
2533
|
+
"FTPServer Source only supports specific file formats: csv, jsonl, parquet."
|
|
2534
|
+
)
|
|
2535
|
+
from ingestr.src.filesystem import readers
|
|
2536
|
+
|
|
2537
|
+
dlt_source_resource = readers(bucket_url, fs, file_glob)
|
|
2538
|
+
return dlt_source_resource.with_resources(endpoint)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ingestr
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.53
|
|
4
4
|
Summary: ingestr is a command-line application that ingests data from various sources and stores them in any database.
|
|
5
5
|
Project-URL: Homepage, https://github.com/bruin-data/ingestr
|
|
6
6
|
Project-URL: Issues, https://github.com/bruin-data/ingestr/issues
|
|
@@ -26,6 +26,7 @@ Requires-Dist: asn1crypto==1.5.1
|
|
|
26
26
|
Requires-Dist: asynch==0.2.4
|
|
27
27
|
Requires-Dist: attrs==25.1.0
|
|
28
28
|
Requires-Dist: backoff==2.2.1
|
|
29
|
+
Requires-Dist: bcrypt==4.3.0
|
|
29
30
|
Requires-Dist: beautifulsoup4==4.13.3
|
|
30
31
|
Requires-Dist: boto3==1.37.1
|
|
31
32
|
Requires-Dist: botocore==1.37.1
|
|
@@ -39,6 +40,7 @@ Requires-Dist: clickhouse-connect==0.8.14
|
|
|
39
40
|
Requires-Dist: clickhouse-driver==0.2.9
|
|
40
41
|
Requires-Dist: clickhouse-sqlalchemy==0.2.7
|
|
41
42
|
Requires-Dist: confluent-kafka==2.8.0
|
|
43
|
+
Requires-Dist: crate==2.0.0
|
|
42
44
|
Requires-Dist: cryptography==44.0.2
|
|
43
45
|
Requires-Dist: curlify==2.2.1
|
|
44
46
|
Requires-Dist: databricks-sql-connector==2.9.3
|
|
@@ -59,6 +61,7 @@ Requires-Dist: flatten-json==0.1.14
|
|
|
59
61
|
Requires-Dist: frozenlist==1.5.0
|
|
60
62
|
Requires-Dist: fsspec==2025.3.2
|
|
61
63
|
Requires-Dist: gcsfs==2025.3.2
|
|
64
|
+
Requires-Dist: geojson==3.2.0
|
|
62
65
|
Requires-Dist: gitdb==4.0.12
|
|
63
66
|
Requires-Dist: gitpython==3.1.44
|
|
64
67
|
Requires-Dist: giturlparse==0.12.0
|
|
@@ -77,7 +80,7 @@ Requires-Dist: google-cloud-storage==3.1.0
|
|
|
77
80
|
Requires-Dist: google-crc32c==1.6.0
|
|
78
81
|
Requires-Dist: google-resumable-media==2.7.2
|
|
79
82
|
Requires-Dist: googleapis-common-protos==1.69.0
|
|
80
|
-
Requires-Dist: greenlet==3.2.
|
|
83
|
+
Requires-Dist: greenlet==3.2.3
|
|
81
84
|
Requires-Dist: grpc-google-iam-v1==0.14.2
|
|
82
85
|
Requires-Dist: grpc-interceptor==0.15.4
|
|
83
86
|
Requires-Dist: grpcio-status==1.62.3
|
|
@@ -113,6 +116,7 @@ Requires-Dist: openpyxl==3.1.5
|
|
|
113
116
|
Requires-Dist: orjson==3.10.15
|
|
114
117
|
Requires-Dist: packaging==24.2
|
|
115
118
|
Requires-Dist: pandas==2.2.3
|
|
119
|
+
Requires-Dist: paramiko==3.5.1
|
|
116
120
|
Requires-Dist: pathvalidate==3.2.3
|
|
117
121
|
Requires-Dist: pendulum==3.0.0
|
|
118
122
|
Requires-Dist: platformdirs==4.3.6
|
|
@@ -137,6 +141,7 @@ Requires-Dist: pygments==2.19.1
|
|
|
137
141
|
Requires-Dist: pyjwt==2.10.1
|
|
138
142
|
Requires-Dist: pymongo==4.11.1
|
|
139
143
|
Requires-Dist: pymysql==1.1.1
|
|
144
|
+
Requires-Dist: pynacl==1.5.0
|
|
140
145
|
Requires-Dist: pyopenssl==25.0.0
|
|
141
146
|
Requires-Dist: pyparsing==3.2.1
|
|
142
147
|
Requires-Dist: pyrate-limiter==3.7.0
|
|
@@ -170,6 +175,7 @@ Requires-Dist: snowflake-sqlalchemy==1.6.1
|
|
|
170
175
|
Requires-Dist: sortedcontainers==2.4.0
|
|
171
176
|
Requires-Dist: soupsieve==2.6
|
|
172
177
|
Requires-Dist: sqlalchemy-bigquery==1.12.1
|
|
178
|
+
Requires-Dist: sqlalchemy-cratedb==0.42.0.dev2
|
|
173
179
|
Requires-Dist: sqlalchemy-hana==2.0.0
|
|
174
180
|
Requires-Dist: sqlalchemy-redshift==0.8.14
|
|
175
181
|
Requires-Dist: sqlalchemy-spanner==1.11.0
|
|
@@ -192,6 +198,7 @@ Requires-Dist: tzdata==2025.1
|
|
|
192
198
|
Requires-Dist: tzlocal==5.3
|
|
193
199
|
Requires-Dist: uritemplate==4.1.1
|
|
194
200
|
Requires-Dist: urllib3==2.3.0
|
|
201
|
+
Requires-Dist: verlib2==0.3.1
|
|
195
202
|
Requires-Dist: wrapt==1.17.2
|
|
196
203
|
Requires-Dist: yarl==1.18.3
|
|
197
204
|
Requires-Dist: zeep==4.3.1
|
|
@@ -292,11 +299,21 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
292
299
|
<td>✅</td>
|
|
293
300
|
<td>✅</td>
|
|
294
301
|
</tr>
|
|
302
|
+
<tr>
|
|
303
|
+
<td>CrateDB</td>
|
|
304
|
+
<td>✅</td>
|
|
305
|
+
<td>❌</td>
|
|
306
|
+
</tr>
|
|
295
307
|
<tr>
|
|
296
308
|
<td>Databricks</td>
|
|
297
309
|
<td>✅</td>
|
|
298
310
|
<td>✅</td>
|
|
299
311
|
</tr>
|
|
312
|
+
<tr>
|
|
313
|
+
<td>IBM Db2</td>
|
|
314
|
+
<td>✅</td>
|
|
315
|
+
<td>-</td>
|
|
316
|
+
</tr>
|
|
300
317
|
<tr>
|
|
301
318
|
<td>DuckDB</td>
|
|
302
319
|
<td>✅</td>
|
|
@@ -307,6 +324,16 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
307
324
|
<td>✅</td>
|
|
308
325
|
<td>-</td>
|
|
309
326
|
</tr>
|
|
327
|
+
<tr>
|
|
328
|
+
<td>Elasticsearch</td>
|
|
329
|
+
<td>✅</td>
|
|
330
|
+
<td>-</td>
|
|
331
|
+
</tr>
|
|
332
|
+
<tr>
|
|
333
|
+
<td>GCP Spanner</td>
|
|
334
|
+
<td>✅</td>
|
|
335
|
+
<td>-</td>
|
|
336
|
+
</tr>
|
|
310
337
|
<tr>
|
|
311
338
|
<td>Local CSV file</td>
|
|
312
339
|
<td>✅</td>
|
|
@@ -393,6 +420,11 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
393
420
|
<td>✅</td>
|
|
394
421
|
<td>-</td>
|
|
395
422
|
</tr>
|
|
423
|
+
<tr>
|
|
424
|
+
<td>Attio</td>
|
|
425
|
+
<td>✅</td>
|
|
426
|
+
<td>-</td>
|
|
427
|
+
</tr>
|
|
396
428
|
<tr>
|
|
397
429
|
<td>Chess.com</td>
|
|
398
430
|
<td>✅</td>
|
|
@@ -404,7 +436,7 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
404
436
|
<td>-</td>
|
|
405
437
|
</tr>
|
|
406
438
|
<tr>
|
|
407
|
-
<td>
|
|
439
|
+
<td>GitHub</td>
|
|
408
440
|
<td>✅</td>
|
|
409
441
|
<td>-</td>
|
|
410
442
|
</tr>
|
|
@@ -447,6 +479,16 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
447
479
|
<td>Personio</td>
|
|
448
480
|
<td>✅</td>
|
|
449
481
|
<td>-</td>
|
|
482
|
+
</tr>
|
|
483
|
+
<tr>
|
|
484
|
+
<td>Phantombuster</td>
|
|
485
|
+
<td>✅</td>
|
|
486
|
+
<td>-</td>
|
|
487
|
+
</tr>
|
|
488
|
+
<tr>
|
|
489
|
+
<td>Pipedrive</td>
|
|
490
|
+
<td>✅</td>
|
|
491
|
+
<td>-</td>
|
|
450
492
|
</tr>
|
|
451
493
|
<tr>
|
|
452
494
|
<td>S3</td>
|
|
@@ -469,7 +511,12 @@ Pull requests are welcome. However, please open an issue first to discuss what y
|
|
|
469
511
|
<td>-</td>
|
|
470
512
|
</tr>
|
|
471
513
|
<tr>
|
|
472
|
-
<td>
|
|
514
|
+
<td>Smartsheets</td>
|
|
515
|
+
<td>✅</td>
|
|
516
|
+
<td>-</td>
|
|
517
|
+
</tr>
|
|
518
|
+
<tr>
|
|
519
|
+
<td>Solidgate</td>
|
|
473
520
|
<td>✅</td>
|
|
474
521
|
<td>-</td>
|
|
475
522
|
</tr>
|
|
@@ -2,16 +2,16 @@ ingestr/conftest.py,sha256=Q03FIJIZpLBbpj55cfCHIKEjc1FCvWJhMF2cidUJKQU,1748
|
|
|
2
2
|
ingestr/main.py,sha256=GkC1hdq8AVGrvolc95zMfjmibI95p2pmFkbgCOVf-Og,26311
|
|
3
3
|
ingestr/src/.gitignore,sha256=8cX1AZTSI0TcdZFGTmS_oyBjpfCzhOEt0DdAo2dFIY8,203
|
|
4
4
|
ingestr/src/blob.py,sha256=onMe5ZHxPXTdcB_s2oGNdMo-XQJ3ajwOsWE9eSTGFmc,1495
|
|
5
|
-
ingestr/src/buildinfo.py,sha256=
|
|
6
|
-
ingestr/src/destinations.py,sha256=
|
|
5
|
+
ingestr/src/buildinfo.py,sha256=TRZbcB7mFeJZcDA_BqTjZvRtxs0161pLvM9EmkNt90U,21
|
|
6
|
+
ingestr/src/destinations.py,sha256=wYE6p_DC9HrQ5KehhgbLxgnkS1P9wE9L21Hw_lgAZ70,16884
|
|
7
7
|
ingestr/src/errors.py,sha256=Ufs4_DfE77_E3vnA1fOQdi6cmuLVNm7_SbFLkL1XPGk,686
|
|
8
|
-
ingestr/src/factory.py,sha256=
|
|
8
|
+
ingestr/src/factory.py,sha256=wDa3F7xbnQcQwfnQIbxWypuJ1G8GGhRpdssWRPtEO_Q,6020
|
|
9
9
|
ingestr/src/filters.py,sha256=LLecXe9QkLFkFLUZ92OXNdcANr1a8edDxrflc2ko_KA,1452
|
|
10
10
|
ingestr/src/http_client.py,sha256=bxqsk6nJNXCo-79gW04B53DQO-yr25vaSsqP0AKtjx4,732
|
|
11
11
|
ingestr/src/loader.py,sha256=9NaWAyfkXdqAZSS-N72Iwo36Lbx4PyqIfaaH1dNdkFs,1712
|
|
12
12
|
ingestr/src/partition.py,sha256=BrIP6wFJvyR7Nus_3ElnfxknUXeCipK_E_bB8kZowfc,969
|
|
13
13
|
ingestr/src/resource.py,sha256=ZqmZxFQVGlF8rFPhBiUB08HES0yoTj8sZ--jKfaaVps,1164
|
|
14
|
-
ingestr/src/sources.py,sha256=
|
|
14
|
+
ingestr/src/sources.py,sha256=sEi-09LXySuhHqUpscHlfQTmQ_7Bgq1GDY_y5mma-sg,87437
|
|
15
15
|
ingestr/src/table_definition.py,sha256=REbAbqdlmUMUuRh8nEQRreWjPVOQ5ZcfqGkScKdCrmk,390
|
|
16
16
|
ingestr/src/time.py,sha256=H_Fk2J4ShXyUM-EMY7MqCLZQhlnZMZvO952bmZPc4yE,254
|
|
17
17
|
ingestr/src/version.py,sha256=J_2xgZ0mKlvuHcjdKCx2nlioneLH0I47JiU_Slr_Nwc,189
|
|
@@ -135,8 +135,8 @@ ingestr/testdata/merge_expected.csv,sha256=DReHqWGnQMsf2PBv_Q2pfjsgvikYFnf1zYcQZ
|
|
|
135
135
|
ingestr/testdata/merge_part1.csv,sha256=Pw8Z9IDKcNU0qQHx1z6BUf4rF_-SxKGFOvymCt4OY9I,185
|
|
136
136
|
ingestr/testdata/merge_part2.csv,sha256=T_GiWxA81SN63_tMOIuemcvboEFeAmbKc7xRXvL9esw,287
|
|
137
137
|
ingestr/tests/unit/test_smartsheets.py,sha256=eiC2CCO4iNJcuN36ONvqmEDryCA1bA1REpayHpu42lk,5058
|
|
138
|
-
ingestr-0.13.
|
|
139
|
-
ingestr-0.13.
|
|
140
|
-
ingestr-0.13.
|
|
141
|
-
ingestr-0.13.
|
|
142
|
-
ingestr-0.13.
|
|
138
|
+
ingestr-0.13.53.dist-info/METADATA,sha256=vVMGPiZ4snksSxZGUKgNl0a3bfSxUBeTrWpUG0lHFhw,14902
|
|
139
|
+
ingestr-0.13.53.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
140
|
+
ingestr-0.13.53.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
|
|
141
|
+
ingestr-0.13.53.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
|
|
142
|
+
ingestr-0.13.53.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|