dbos 1.15.0a1__py3-none-any.whl → 1.15.0a2__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 dbos might be problematic. Click here for more details.
- dbos/_client.py +17 -21
- dbos/_core.py +3 -1
- dbos/_dbos.py +10 -14
- dbos/_dbos_config.py +15 -13
- dbos/_workflow_commands.py +9 -5
- dbos/cli/cli.py +7 -5
- dbos/cli/migration.py +15 -10
- {dbos-1.15.0a1.dist-info → dbos-1.15.0a2.dist-info}/METADATA +1 -1
- {dbos-1.15.0a1.dist-info → dbos-1.15.0a2.dist-info}/RECORD +12 -12
- {dbos-1.15.0a1.dist-info → dbos-1.15.0a2.dist-info}/WHEEL +0 -0
- {dbos-1.15.0a1.dist-info → dbos-1.15.0a2.dist-info}/entry_points.txt +0 -0
- {dbos-1.15.0a1.dist-info → dbos-1.15.0a2.dist-info}/licenses/LICENSE +0 -0
dbos/_client.py
CHANGED
|
@@ -22,11 +22,7 @@ from dbos._sys_db import SystemDatabase
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from dbos._dbos import WorkflowHandle, WorkflowHandleAsync
|
|
24
24
|
|
|
25
|
-
from dbos._dbos_config import
|
|
26
|
-
get_application_database_url,
|
|
27
|
-
get_system_database_url,
|
|
28
|
-
is_valid_database_url,
|
|
29
|
-
)
|
|
25
|
+
from dbos._dbos_config import get_system_database_url, is_valid_database_url
|
|
30
26
|
from dbos._error import DBOSException, DBOSNonExistentWorkflowError
|
|
31
27
|
from dbos._registrations import DEFAULT_MAX_RECOVERY_ATTEMPTS
|
|
32
28
|
from dbos._serialization import WorkflowInputs
|
|
@@ -118,6 +114,9 @@ class WorkflowHandleClientAsyncPolling(Generic[R]):
|
|
|
118
114
|
|
|
119
115
|
|
|
120
116
|
class DBOSClient:
|
|
117
|
+
|
|
118
|
+
_app_db: ApplicationDatabase | None = None
|
|
119
|
+
|
|
121
120
|
def __init__(
|
|
122
121
|
self,
|
|
123
122
|
database_url: Optional[str] = None, # DEPRECATED
|
|
@@ -126,13 +125,8 @@ class DBOSClient:
|
|
|
126
125
|
application_database_url: Optional[str] = None,
|
|
127
126
|
system_database: Optional[str] = None, # DEPRECATED
|
|
128
127
|
):
|
|
129
|
-
application_database_url =
|
|
130
|
-
|
|
131
|
-
"system_database_url": system_database_url,
|
|
132
|
-
"database_url": (
|
|
133
|
-
database_url if database_url else application_database_url
|
|
134
|
-
),
|
|
135
|
-
}
|
|
128
|
+
application_database_url = (
|
|
129
|
+
database_url if database_url else application_database_url
|
|
136
130
|
)
|
|
137
131
|
system_database_url = get_system_database_url(
|
|
138
132
|
{
|
|
@@ -142,7 +136,8 @@ class DBOSClient:
|
|
|
142
136
|
}
|
|
143
137
|
)
|
|
144
138
|
assert is_valid_database_url(system_database_url)
|
|
145
|
-
|
|
139
|
+
if application_database_url:
|
|
140
|
+
assert is_valid_database_url(application_database_url)
|
|
146
141
|
# We only create database connections but do not run migrations
|
|
147
142
|
self._sys_db = SystemDatabase.create(
|
|
148
143
|
system_database_url=system_database_url,
|
|
@@ -153,14 +148,15 @@ class DBOSClient:
|
|
|
153
148
|
},
|
|
154
149
|
)
|
|
155
150
|
self._sys_db.check_connection()
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
151
|
+
if application_database_url:
|
|
152
|
+
self._app_db = ApplicationDatabase.create(
|
|
153
|
+
database_url=application_database_url,
|
|
154
|
+
engine_kwargs={
|
|
155
|
+
"pool_timeout": 30,
|
|
156
|
+
"max_overflow": 0,
|
|
157
|
+
"pool_size": 2,
|
|
158
|
+
},
|
|
159
|
+
)
|
|
164
160
|
|
|
165
161
|
def destroy(self) -> None:
|
|
166
162
|
self._sys_db.destroy()
|
dbos/_core.py
CHANGED
|
@@ -896,7 +896,9 @@ def decorate_transaction(
|
|
|
896
896
|
raise DBOSWorkflowCancelledError(
|
|
897
897
|
f"Workflow {ctx.workflow_id} is cancelled. Aborting transaction {transaction_name}."
|
|
898
898
|
)
|
|
899
|
-
|
|
899
|
+
assert (
|
|
900
|
+
dbos._app_db
|
|
901
|
+
), "Transactions can only be used if DBOS is configured with an application_database_url"
|
|
900
902
|
with dbos._app_db.sessionmaker() as session:
|
|
901
903
|
attributes: TracedAttributes = {
|
|
902
904
|
"name": transaction_name,
|
dbos/_dbos.py
CHANGED
|
@@ -409,13 +409,8 @@ class DBOS:
|
|
|
409
409
|
return rv
|
|
410
410
|
|
|
411
411
|
@property
|
|
412
|
-
def _app_db(self) -> ApplicationDatabase:
|
|
413
|
-
|
|
414
|
-
raise DBOSException(
|
|
415
|
-
"Application database accessed before DBOS was launched"
|
|
416
|
-
)
|
|
417
|
-
rv: ApplicationDatabase = self._app_db_field
|
|
418
|
-
return rv
|
|
412
|
+
def _app_db(self) -> ApplicationDatabase | None:
|
|
413
|
+
return self._app_db_field
|
|
419
414
|
|
|
420
415
|
@property
|
|
421
416
|
def _admin_server(self) -> AdminServer:
|
|
@@ -448,7 +443,6 @@ class DBOS:
|
|
|
448
443
|
dbos_logger.info(f"Application version: {GlobalParams.app_version}")
|
|
449
444
|
self._executor_field = ThreadPoolExecutor(max_workers=sys.maxsize)
|
|
450
445
|
self._background_event_loop.start()
|
|
451
|
-
assert self._config["database_url"] is not None
|
|
452
446
|
assert self._config["database"]["sys_db_engine_kwargs"] is not None
|
|
453
447
|
self._sys_db_field = SystemDatabase.create(
|
|
454
448
|
system_database_url=get_system_database_url(self._config),
|
|
@@ -456,18 +450,20 @@ class DBOS:
|
|
|
456
450
|
debug_mode=debug_mode,
|
|
457
451
|
)
|
|
458
452
|
assert self._config["database"]["db_engine_kwargs"] is not None
|
|
459
|
-
self.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
453
|
+
if self._config["database_url"]:
|
|
454
|
+
self._app_db_field = ApplicationDatabase.create(
|
|
455
|
+
database_url=self._config["database_url"],
|
|
456
|
+
engine_kwargs=self._config["database"]["db_engine_kwargs"],
|
|
457
|
+
debug_mode=debug_mode,
|
|
458
|
+
)
|
|
464
459
|
|
|
465
460
|
if debug_mode:
|
|
466
461
|
return
|
|
467
462
|
|
|
468
463
|
# Run migrations for the system and application databases
|
|
469
464
|
self._sys_db.run_migrations()
|
|
470
|
-
self._app_db
|
|
465
|
+
if self._app_db:
|
|
466
|
+
self._app_db.run_migrations()
|
|
471
467
|
|
|
472
468
|
admin_port = self._config.get("runtimeConfig", {}).get("admin_port")
|
|
473
469
|
if admin_port is None:
|
dbos/_dbos_config.py
CHANGED
|
@@ -408,22 +408,20 @@ def process_config(
|
|
|
408
408
|
url = url.set(database=f"{url.database}{SystemSchema.sysdb_suffix}")
|
|
409
409
|
data["system_database_url"] = url.render_as_string(hide_password=False)
|
|
410
410
|
|
|
411
|
-
# If a system database URL is provided but not an application database URL,
|
|
412
|
-
#
|
|
411
|
+
# If a system database URL is provided but not an application database URL,
|
|
412
|
+
# do not create an application database.
|
|
413
413
|
if data.get("system_database_url") and not data.get("database_url"):
|
|
414
414
|
assert data["system_database_url"]
|
|
415
|
-
data["database_url"] =
|
|
415
|
+
data["database_url"] = None
|
|
416
416
|
|
|
417
|
-
# If neither URL is provided, use a default SQLite database URL.
|
|
417
|
+
# If neither URL is provided, use a default SQLite system database URL.
|
|
418
418
|
if not data.get("database_url") and not data.get("system_database_url"):
|
|
419
419
|
_app_db_name = _app_name_to_db_name(data["name"])
|
|
420
|
-
data["system_database_url"] =
|
|
421
|
-
|
|
422
|
-
)
|
|
420
|
+
data["system_database_url"] = f"sqlite:///{_app_db_name}.sqlite"
|
|
421
|
+
data["database_url"] = None
|
|
423
422
|
|
|
424
423
|
configure_db_engine_parameters(data["database"], connect_timeout=connect_timeout)
|
|
425
424
|
|
|
426
|
-
assert data["database_url"] is not None
|
|
427
425
|
assert data["system_database_url"] is not None
|
|
428
426
|
# Pretty-print connection information, respecting log level
|
|
429
427
|
if not silent and logs["logLevel"] == "INFO" or logs["logLevel"] == "DEBUG":
|
|
@@ -431,7 +429,12 @@ def process_config(
|
|
|
431
429
|
hide_password=True
|
|
432
430
|
)
|
|
433
431
|
print(f"DBOS system database URL: {printable_sys_db_url}")
|
|
434
|
-
if data["database_url"]
|
|
432
|
+
if data["database_url"]:
|
|
433
|
+
printable_app_db_url = make_url(data["database_url"]).render_as_string(
|
|
434
|
+
hide_password=True
|
|
435
|
+
)
|
|
436
|
+
print(f"DBOS application database URL: {printable_app_db_url}")
|
|
437
|
+
if data["system_database_url"].startswith("sqlite"):
|
|
435
438
|
print(
|
|
436
439
|
f"Using SQLite as a system database. The SQLite system database is for development and testing. PostgreSQL is recommended for production use."
|
|
437
440
|
)
|
|
@@ -615,12 +618,11 @@ def get_system_database_url(config: ConfigFile) -> str:
|
|
|
615
618
|
)
|
|
616
619
|
|
|
617
620
|
|
|
618
|
-
def get_application_database_url(config: ConfigFile) -> str:
|
|
621
|
+
def get_application_database_url(config: ConfigFile) -> str | None:
|
|
619
622
|
# For backwards compatibility, the application database URL is "database_url"
|
|
620
623
|
if config.get("database_url"):
|
|
621
624
|
assert config["database_url"]
|
|
622
625
|
return config["database_url"]
|
|
623
626
|
else:
|
|
624
|
-
# If the application database URL is not specified,
|
|
625
|
-
|
|
626
|
-
return config["system_database_url"]
|
|
627
|
+
# If the application database URL is not specified, return None
|
|
628
|
+
return None
|
dbos/_workflow_commands.py
CHANGED
|
@@ -98,10 +98,10 @@ def get_workflow(sys_db: SystemDatabase, workflow_id: str) -> Optional[WorkflowS
|
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
def list_workflow_steps(
|
|
101
|
-
sys_db: SystemDatabase, app_db: ApplicationDatabase, workflow_id: str
|
|
101
|
+
sys_db: SystemDatabase, app_db: Optional[ApplicationDatabase], workflow_id: str
|
|
102
102
|
) -> List[StepInfo]:
|
|
103
103
|
steps = sys_db.get_workflow_steps(workflow_id)
|
|
104
|
-
transactions = app_db.get_transactions(workflow_id)
|
|
104
|
+
transactions = app_db.get_transactions(workflow_id) if app_db else []
|
|
105
105
|
merged_steps = steps + transactions
|
|
106
106
|
merged_steps.sort(key=lambda step: step["function_id"])
|
|
107
107
|
return merged_steps
|
|
@@ -109,7 +109,7 @@ def list_workflow_steps(
|
|
|
109
109
|
|
|
110
110
|
def fork_workflow(
|
|
111
111
|
sys_db: SystemDatabase,
|
|
112
|
-
app_db: ApplicationDatabase,
|
|
112
|
+
app_db: Optional[ApplicationDatabase],
|
|
113
113
|
workflow_id: str,
|
|
114
114
|
start_step: int,
|
|
115
115
|
*,
|
|
@@ -122,7 +122,8 @@ def fork_workflow(
|
|
|
122
122
|
ctx.id_assigned_for_next_workflow = ""
|
|
123
123
|
else:
|
|
124
124
|
forked_workflow_id = str(uuid.uuid4())
|
|
125
|
-
app_db
|
|
125
|
+
if app_db:
|
|
126
|
+
app_db.clone_workflow_transactions(workflow_id, forked_workflow_id, start_step)
|
|
126
127
|
sys_db.fork_workflow(
|
|
127
128
|
workflow_id,
|
|
128
129
|
forked_workflow_id,
|
|
@@ -145,7 +146,10 @@ def garbage_collect(
|
|
|
145
146
|
)
|
|
146
147
|
if result is not None:
|
|
147
148
|
cutoff_epoch_timestamp_ms, pending_workflow_ids = result
|
|
148
|
-
dbos._app_db
|
|
149
|
+
if dbos._app_db:
|
|
150
|
+
dbos._app_db.garbage_collect(
|
|
151
|
+
cutoff_epoch_timestamp_ms, pending_workflow_ids
|
|
152
|
+
)
|
|
149
153
|
|
|
150
154
|
|
|
151
155
|
def global_timeout(dbos: "DBOS", cutoff_epoch_timestamp_ms: int) -> None:
|
dbos/cli/cli.py
CHANGED
|
@@ -38,7 +38,7 @@ class DefaultEncoder(json.JSONEncoder):
|
|
|
38
38
|
|
|
39
39
|
def _get_db_url(
|
|
40
40
|
*, system_database_url: Optional[str], application_database_url: Optional[str]
|
|
41
|
-
) -> Tuple[str, str]:
|
|
41
|
+
) -> Tuple[str, str | None]:
|
|
42
42
|
"""
|
|
43
43
|
Get the database URL to use for the DBOS application.
|
|
44
44
|
Order of precedence:
|
|
@@ -294,7 +294,8 @@ def migrate(
|
|
|
294
294
|
)
|
|
295
295
|
|
|
296
296
|
typer.echo(f"Starting DBOS migrations")
|
|
297
|
-
|
|
297
|
+
if application_database_url:
|
|
298
|
+
typer.echo(f"Application database: {sa.make_url(application_database_url)}")
|
|
298
299
|
typer.echo(f"System database: {sa.make_url(system_database_url)}")
|
|
299
300
|
|
|
300
301
|
# First, run DBOS migrations on the system database and the application database
|
|
@@ -305,9 +306,10 @@ def migrate(
|
|
|
305
306
|
|
|
306
307
|
# Next, assign permissions on the DBOS schema to the application role, if any
|
|
307
308
|
if application_role:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
if application_database_url:
|
|
310
|
+
grant_dbos_schema_permissions(
|
|
311
|
+
database_url=application_database_url, role_name=application_role
|
|
312
|
+
)
|
|
311
313
|
grant_dbos_schema_permissions(
|
|
312
314
|
database_url=system_database_url, role_name=application_role
|
|
313
315
|
)
|
dbos/cli/migration.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
1
3
|
import sqlalchemy as sa
|
|
2
4
|
import typer
|
|
3
5
|
|
|
@@ -5,7 +7,9 @@ from dbos._app_db import ApplicationDatabase
|
|
|
5
7
|
from dbos._sys_db import SystemDatabase
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
def migrate_dbos_databases(
|
|
10
|
+
def migrate_dbos_databases(
|
|
11
|
+
app_database_url: Optional[str], system_database_url: str
|
|
12
|
+
) -> None:
|
|
9
13
|
app_db = None
|
|
10
14
|
sys_db = None
|
|
11
15
|
try:
|
|
@@ -17,16 +21,17 @@ def migrate_dbos_databases(app_database_url: str, system_database_url: str) -> N
|
|
|
17
21
|
"pool_size": 2,
|
|
18
22
|
},
|
|
19
23
|
)
|
|
20
|
-
app_db = ApplicationDatabase.create(
|
|
21
|
-
database_url=app_database_url,
|
|
22
|
-
engine_kwargs={
|
|
23
|
-
"pool_timeout": 30,
|
|
24
|
-
"max_overflow": 0,
|
|
25
|
-
"pool_size": 2,
|
|
26
|
-
},
|
|
27
|
-
)
|
|
28
24
|
sys_db.run_migrations()
|
|
29
|
-
|
|
25
|
+
if app_database_url:
|
|
26
|
+
app_db = ApplicationDatabase.create(
|
|
27
|
+
database_url=app_database_url,
|
|
28
|
+
engine_kwargs={
|
|
29
|
+
"pool_timeout": 30,
|
|
30
|
+
"max_overflow": 0,
|
|
31
|
+
"pool_size": 2,
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
app_db.run_migrations()
|
|
30
35
|
except Exception as e:
|
|
31
36
|
typer.echo(f"DBOS migrations failed: {e}")
|
|
32
37
|
raise typer.Exit(code=1)
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
dbos-1.15.
|
|
2
|
-
dbos-1.15.
|
|
3
|
-
dbos-1.15.
|
|
4
|
-
dbos-1.15.
|
|
1
|
+
dbos-1.15.0a2.dist-info/METADATA,sha256=STEFFiAPM8uZbNbNRPz81v65hcKlFKT1hiL52v7yjo8,13021
|
|
2
|
+
dbos-1.15.0a2.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
|
3
|
+
dbos-1.15.0a2.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
|
|
4
|
+
dbos-1.15.0a2.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
|
|
5
5
|
dbos/__init__.py,sha256=pT4BuNLDCrIQX27vQG8NlfxX6PZRU7r9miq4thJTszU,982
|
|
6
6
|
dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
|
|
7
7
|
dbos/_admin_server.py,sha256=e8ELhcDWqR3_PNobnNgUvLGh5lzZq0yFSF6dvtzoQRI,16267
|
|
8
8
|
dbos/_app_db.py,sha256=GsV-uYU0QsChWwQDxnrh8_iiZ_zMQB-bsP2jPGIe2aM,16094
|
|
9
9
|
dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
|
|
10
|
-
dbos/_client.py,sha256=
|
|
10
|
+
dbos/_client.py,sha256=zfFQhj_mf4NS85Nlf1xbXwMQVX_mu3UaU7sb8uE5peM,18794
|
|
11
11
|
dbos/_conductor/conductor.py,sha256=3E_hL3c9g9yWqKZkvI6KA0-ZzPMPRo06TOzT1esMiek,24114
|
|
12
12
|
dbos/_conductor/protocol.py,sha256=q3rgLxINFtWFigdOONc-4gX4vn66UmMlJQD6Kj8LnL4,7420
|
|
13
13
|
dbos/_context.py,sha256=cJDxVbswTLXKE5MV4Hmg6gpIX3Dd5mBTG-4lmofWP9E,27668
|
|
14
|
-
dbos/_core.py,sha256=
|
|
14
|
+
dbos/_core.py,sha256=13DNN_fpSIs42NquV80XsHV7yKwY_adKP03h_xhXok4,50493
|
|
15
15
|
dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
|
|
16
|
-
dbos/_dbos.py,sha256=
|
|
17
|
-
dbos/_dbos_config.py,sha256=
|
|
16
|
+
dbos/_dbos.py,sha256=ZrNrFUoND2t8UorqPLjeDvfOP73AasOXqpeuSS6Rz7E,57836
|
|
17
|
+
dbos/_dbos_config.py,sha256=sWXd9RuWGmhkd7j2SraxDWQir_-F2p0SIqGO61ILeyk,25391
|
|
18
18
|
dbos/_debouncer.py,sha256=VmGq1_ZIQ79fnH14LEhdoqxKWp6rlEwzsUwumwAMgTQ,15095
|
|
19
19
|
dbos/_debug.py,sha256=0MfgNqutCUhI4PEmmra9x7f3DiFE_0nscfUCHdLimEY,1415
|
|
20
20
|
dbos/_docker_pg_helper.py,sha256=xySum4hTA8TVMBODoG19u4cXQAB1vCock-jwM2pnmSI,7791
|
|
@@ -48,12 +48,12 @@ dbos/_templates/dbos-db-starter/migrations/create_table.py.dbos,sha256=pVm2Q0Asx
|
|
|
48
48
|
dbos/_templates/dbos-db-starter/start_postgres_docker.py,sha256=lQVLlYO5YkhGPEgPqwGc7Y8uDKse9HsWv5fynJEFJHM,1681
|
|
49
49
|
dbos/_tracer.py,sha256=PHbD7iTEkHk7z4B9hc-wPgi2dPTeI1rhZgLI33TQEeM,3786
|
|
50
50
|
dbos/_utils.py,sha256=ZdoM1MDbHnlJrh31zfhp3iX62bAxK1kyvMwXnltC_84,1779
|
|
51
|
-
dbos/_workflow_commands.py,sha256=
|
|
51
|
+
dbos/_workflow_commands.py,sha256=k-i1bCfNrux43BHLT8wQ-l-MVZX3D6LGZLH7-uuiDRo,4951
|
|
52
52
|
dbos/cli/_github_init.py,sha256=R_94Fnn40CAmPy-zM00lwHi0ndyfv57TmIooADjmag4,3378
|
|
53
53
|
dbos/cli/_template_init.py,sha256=AltKk256VocgvxLpuTxpjJyACrdHFjbGoqYhHzeLae4,2649
|
|
54
|
-
dbos/cli/cli.py,sha256
|
|
55
|
-
dbos/cli/migration.py,sha256=
|
|
54
|
+
dbos/cli/cli.py,sha256=8fn8hseZWWseJiJMo21_mWYfMqgM2y7l_3UbMP0YNMI,26724
|
|
55
|
+
dbos/cli/migration.py,sha256=vaYxHy0k5KgEuoOQUl6R9oxKv4V5nKKpaVhRbkLDXpo,3440
|
|
56
56
|
dbos/dbos-config.schema.json,sha256=LyUT1DOTaAwOP6suxQGS5KemVIqXGPyu_q7Hbo0neA8,6192
|
|
57
57
|
dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
|
|
58
58
|
version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
|
|
59
|
-
dbos-1.15.
|
|
59
|
+
dbos-1.15.0a2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|