dbos 0.10.0a2__tar.gz → 0.11.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.
Files changed (75) hide show
  1. {dbos-0.10.0a2 → dbos-0.11.0}/PKG-INFO +9 -9
  2. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/cli.py +1 -10
  3. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/dbos-config.schema.json +1 -2
  4. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/dbos_config.py +21 -0
  5. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/system_database.py +8 -3
  6. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/dbos-config.yaml.dbos +0 -1
  7. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/migrations/env.py.dbos +8 -1
  8. {dbos-0.10.0a2 → dbos-0.11.0}/pyproject.toml +18 -18
  9. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_config.py +92 -11
  10. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_schema_migration.py +7 -1
  11. {dbos-0.10.0a2 → dbos-0.11.0}/LICENSE +0 -0
  12. {dbos-0.10.0a2 → dbos-0.11.0}/README.md +0 -0
  13. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/__init__.py +0 -0
  14. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/admin_sever.py +0 -0
  15. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/application_database.py +0 -0
  16. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/context.py +0 -0
  17. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/core.py +0 -0
  18. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/dbos.py +0 -0
  19. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/decorators.py +0 -0
  20. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/error.py +0 -0
  21. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/fastapi.py +0 -0
  22. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/flask.py +0 -0
  23. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/kafka.py +0 -0
  24. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/kafka_message.py +0 -0
  25. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/logger.py +0 -0
  26. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/migrations/env.py +0 -0
  27. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/migrations/script.py.mako +0 -0
  28. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  29. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  30. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  31. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  32. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  33. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  34. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/py.typed +0 -0
  35. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/queue.py +0 -0
  36. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/recovery.py +0 -0
  37. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/registrations.py +0 -0
  38. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/request.py +0 -0
  39. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/roles.py +0 -0
  40. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/scheduler/croniter.py +0 -0
  41. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/scheduler/scheduler.py +0 -0
  42. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/schemas/__init__.py +0 -0
  43. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/schemas/application_database.py +0 -0
  44. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/schemas/system_database.py +0 -0
  45. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/README.md +0 -0
  46. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/__package/__init__.py +0 -0
  47. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/__package/main.py +0 -0
  48. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/__package/schema.py +0 -0
  49. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/alembic.ini +0 -0
  50. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/migrations/script.py.mako +0 -0
  51. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/migrations/versions/2024_07_31_180642_init.py +0 -0
  52. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/templates/hello/start_postgres_docker.py +0 -0
  53. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/tracer.py +0 -0
  54. {dbos-0.10.0a2 → dbos-0.11.0}/dbos/utils.py +0 -0
  55. {dbos-0.10.0a2 → dbos-0.11.0}/tests/__init__.py +0 -0
  56. {dbos-0.10.0a2 → dbos-0.11.0}/tests/atexit_no_ctor.py +0 -0
  57. {dbos-0.10.0a2 → dbos-0.11.0}/tests/atexit_no_launch.py +0 -0
  58. {dbos-0.10.0a2 → dbos-0.11.0}/tests/classdefs.py +0 -0
  59. {dbos-0.10.0a2 → dbos-0.11.0}/tests/conftest.py +0 -0
  60. {dbos-0.10.0a2 → dbos-0.11.0}/tests/more_classdefs.py +0 -0
  61. {dbos-0.10.0a2 → dbos-0.11.0}/tests/scheduler/test_croniter.py +0 -0
  62. {dbos-0.10.0a2 → dbos-0.11.0}/tests/scheduler/test_scheduler.py +0 -0
  63. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_admin_server.py +0 -0
  64. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_classdecorators.py +0 -0
  65. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_concurrency.py +0 -0
  66. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_dbos.py +0 -0
  67. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_failures.py +0 -0
  68. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_fastapi.py +0 -0
  69. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_fastapi_roles.py +0 -0
  70. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_flask.py +0 -0
  71. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_kafka.py +0 -0
  72. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_package.py +0 -0
  73. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_queue.py +0 -0
  74. {dbos-0.10.0a2 → dbos-0.11.0}/tests/test_singleton.py +0 -0
  75. {dbos-0.10.0a2 → dbos-0.11.0}/version/__init__.py +0 -0
@@ -1,24 +1,24 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.10.0a2
3
+ Version: 0.11.0
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
7
7
  Requires-Python: >=3.9
8
8
  Requires-Dist: pyyaml>=6.0.2
9
9
  Requires-Dist: jsonschema>=4.23.0
10
- Requires-Dist: alembic>=1.13.2
10
+ Requires-Dist: alembic>=1.13.3
11
11
  Requires-Dist: typing-extensions>=4.12.2; python_version < "3.10"
12
- Requires-Dist: typer>=0.12.3
13
- Requires-Dist: jsonpickle>=3.2.2
14
- Requires-Dist: opentelemetry-api>=1.26.0
15
- Requires-Dist: opentelemetry-sdk>=1.26.0
16
- Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.26.0
12
+ Requires-Dist: typer>=0.12.5
13
+ Requires-Dist: jsonpickle>=3.3.0
14
+ Requires-Dist: opentelemetry-api>=1.27.0
15
+ Requires-Dist: opentelemetry-sdk>=1.27.0
16
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27.0
17
17
  Requires-Dist: python-dateutil>=2.9.0.post0
18
- Requires-Dist: fastapi[standard]>=0.112.1
18
+ Requires-Dist: fastapi[standard]>=0.115.2
19
19
  Requires-Dist: psutil>=6.0.0
20
20
  Requires-Dist: tomlkit>=0.13.2
21
- Requires-Dist: psycopg>=3.2.1
21
+ Requires-Dist: psycopg[binary]>=3.1
22
22
  Description-Content-Type: text/markdown
23
23
 
24
24
 
@@ -17,6 +17,7 @@ from typing_extensions import Annotated
17
17
 
18
18
  from dbos import load_config
19
19
  from dbos.application_database import ApplicationDatabase
20
+ from dbos.dbos_config import is_valid_app_name
20
21
  from dbos.system_database import SystemDatabase
21
22
 
22
23
  app = typer.Typer()
@@ -125,11 +126,9 @@ def copy_template(src_dir: str, project_name: str, config_mode: bool) -> None:
125
126
  dst_dir = path.abspath(".")
126
127
 
127
128
  package_name = project_name.replace("-", "_")
128
- db_name = package_name if not package_name[0].isdigit() else f"_{package_name}"
129
129
  ctx = {
130
130
  "project_name": project_name,
131
131
  "package_name": package_name,
132
- "db_name": db_name,
133
132
  "migration_command": "alembic upgrade head",
134
133
  }
135
134
 
@@ -167,14 +166,6 @@ def get_project_name() -> typing.Union[str, None]:
167
166
  return name
168
167
 
169
168
 
170
- def is_valid_app_name(name: str) -> bool:
171
- name_len = len(name)
172
- if name_len < 3 or name_len > 30:
173
- return False
174
- match = re.match("^[a-z0-9-_]+$", name)
175
- return True if match != None else False
176
-
177
-
178
169
  @app.command()
179
170
  def init(
180
171
  project_name: Annotated[
@@ -86,8 +86,7 @@
86
86
  "hostname",
87
87
  "port",
88
88
  "username",
89
- "password",
90
- "app_db_name"
89
+ "password"
91
90
  ]
92
91
  },
93
92
  "telemetry": {
@@ -167,6 +167,14 @@ def load_config(config_file_path: str = "dbos-config.yaml") -> ConfigFile:
167
167
 
168
168
  data = cast(ConfigFile, data)
169
169
 
170
+ if not is_valid_app_name(data["name"]):
171
+ raise DBOSInitializationError(
172
+ f'Invalid app name {data["name"]}. App names must be between 3 and 30 characters and contain only alphanumeric characters, dashes, and underscores.'
173
+ )
174
+
175
+ if "app_db_name" not in data["database"]:
176
+ data["database"]["app_db_name"] = app_name_to_db_name(data["name"])
177
+
170
178
  if "local_suffix" in data["database"] and data["database"]["local_suffix"]:
171
179
  data["database"]["app_db_name"] = f"{data['database']['app_db_name']}_local"
172
180
 
@@ -174,6 +182,19 @@ def load_config(config_file_path: str = "dbos-config.yaml") -> ConfigFile:
174
182
  return data # type: ignore
175
183
 
176
184
 
185
+ def is_valid_app_name(name: str) -> bool:
186
+ name_len = len(name)
187
+ if name_len < 3 or name_len > 30:
188
+ return False
189
+ match = re.match("^[a-z0-9-_]+$", name)
190
+ return True if match != None else False
191
+
192
+
193
+ def app_name_to_db_name(app_name: str) -> str:
194
+ name = app_name.replace("-", "_")
195
+ return name if not name[0].isdigit() else f"_{name}"
196
+
197
+
177
198
  def set_env_vars(config: ConfigFile) -> None:
178
199
  for env, value in config.get("env", {}).items():
179
200
  if value is not None:
@@ -1,5 +1,6 @@
1
1
  import datetime
2
2
  import os
3
+ import re
3
4
  import threading
4
5
  import time
5
6
  from enum import Enum
@@ -203,9 +204,13 @@ class SystemDatabase:
203
204
  )
204
205
  alembic_cfg = Config()
205
206
  alembic_cfg.set_main_option("script_location", migration_dir)
206
- alembic_cfg.set_main_option(
207
- "sqlalchemy.url", self.engine.url.render_as_string(hide_password=False)
207
+ # Alembic requires the % in URL-escaped parameters to itself be escaped to %%.
208
+ escaped_conn_string = re.sub(
209
+ r"%(?=[0-9A-Fa-f]{2})",
210
+ "%%",
211
+ self.engine.url.render_as_string(hide_password=False),
208
212
  )
213
+ alembic_cfg.set_main_option("sqlalchemy.url", escaped_conn_string)
209
214
  command.upgrade(alembic_cfg, "head")
210
215
 
211
216
  self.notification_conn: Optional[psycopg.connection.Connection] = None
@@ -793,7 +798,7 @@ class SystemDatabase:
793
798
  self.notification_conn.execute("LISTEN dbos_workflow_events_channel")
794
799
 
795
800
  while self._run_background_processes:
796
- gen = self.notification_conn.notifies(timeout=60)
801
+ gen = self.notification_conn.notifies()
797
802
  for notify in gen:
798
803
  channel = notify.channel
799
804
  dbos_logger.debug(
@@ -13,7 +13,6 @@ database:
13
13
  port: 5432
14
14
  username: postgres
15
15
  password: ${PGPASSWORD}
16
- app_db_name: ${db_name}
17
16
  migrate:
18
17
  - ${migration_command}
19
18
  telemetry:
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from logging.config import fileConfig
2
3
 
3
4
  from alembic import context
@@ -15,7 +16,13 @@ if config.config_file_name is not None:
15
16
  fileConfig(config.config_file_name)
16
17
 
17
18
  # programmatically set the sqlalchemy.url field from DBOS Config
18
- config.set_main_option("sqlalchemy.url", get_dbos_database_url())
19
+ # Alembic requires the % in URL-escaped parameters to itself be escaped to %%.
20
+ escaped_conn_string = re.sub(
21
+ r"%(?=[0-9A-Fa-f]{2})",
22
+ "%%",
23
+ get_dbos_database_url(),
24
+ )
25
+ config.set_main_option("sqlalchemy.url", escaped_conn_string)
19
26
 
20
27
  # add your model's MetaData object here
21
28
  # for 'autogenerate' support
@@ -8,22 +8,22 @@ authors = [
8
8
  dependencies = [
9
9
  "pyyaml>=6.0.2",
10
10
  "jsonschema>=4.23.0",
11
- "alembic>=1.13.2",
11
+ "alembic>=1.13.3",
12
12
  "typing-extensions>=4.12.2; python_version < \"3.10\"",
13
- "typer>=0.12.3",
14
- "jsonpickle>=3.2.2",
15
- "opentelemetry-api>=1.26.0",
16
- "opentelemetry-sdk>=1.26.0",
17
- "opentelemetry-exporter-otlp-proto-http>=1.26.0",
13
+ "typer>=0.12.5",
14
+ "jsonpickle>=3.3.0",
15
+ "opentelemetry-api>=1.27.0",
16
+ "opentelemetry-sdk>=1.27.0",
17
+ "opentelemetry-exporter-otlp-proto-http>=1.27.0",
18
18
  "python-dateutil>=2.9.0.post0",
19
- "fastapi[standard]>=0.112.1",
19
+ "fastapi[standard]>=0.115.2",
20
20
  "psutil>=6.0.0",
21
21
  "tomlkit>=0.13.2",
22
- "psycopg>=3.2.1",
22
+ "psycopg[binary]>=3.1",
23
23
  ]
24
24
  requires-python = ">=3.9"
25
25
  readme = "README.md"
26
- version = "0.10.0a2"
26
+ version = "0.11.0"
27
27
 
28
28
  [project.license]
29
29
  text = "MIT"
@@ -46,26 +46,26 @@ version_format = "version:format_version"
46
46
 
47
47
  [tool.pdm.dev-dependencies]
48
48
  dev = [
49
- "pytest>=8.3.2",
50
- "mypy>=1.11.1",
49
+ "pytest>=8.3.3",
50
+ "mypy>=1.12.0",
51
51
  "pytest-mock>=3.14.0",
52
52
  "types-PyYAML>=6.0.12.20240808",
53
53
  "types-jsonschema>=4.23.0.20240813",
54
- "black>=24.8.0",
55
- "pre-commit>=3.8.0",
54
+ "black>=24.10.0",
55
+ "pre-commit>=4.0.1",
56
56
  "isort>=5.13.2",
57
57
  "types-psutil>=6.0.0.20240621",
58
58
  "requests>=2.32.3",
59
- "types-requests>=2.32.0.20240712",
60
- "httpx>=0.27.0",
61
- "pytz>=2024.1",
59
+ "types-requests>=2.32.0.20240914",
60
+ "httpx>=0.27.2",
61
+ "pytz>=2024.2",
62
62
  "GitPython>=3.1.43",
63
- "confluent-kafka>=2.5.3",
63
+ "confluent-kafka>=2.6.0",
64
64
  "types-confluent-kafka>=1.2.2",
65
65
  "flask>=3.0.3",
66
66
  "pytest-order>=1.3.0",
67
67
  "pyjwt>=2.9.0",
68
- "pdm-backend>=2.3.3",
68
+ "pdm-backend>=2.4.2",
69
69
  ]
70
70
 
71
71
  [tool.black]
@@ -27,7 +27,7 @@ def generate_mock_open(filename, mock_data):
27
27
 
28
28
  def test_valid_config(mocker):
29
29
  mock_config = """
30
- name: "some app"
30
+ name: "some-app"
31
31
  language: "python"
32
32
  runtimeConfig:
33
33
  start:
@@ -51,7 +51,7 @@ def test_valid_config(mocker):
51
51
  )
52
52
 
53
53
  configFile = load_config(mock_filename)
54
- assert configFile["name"] == "some app"
54
+ assert configFile["name"] == "some-app"
55
55
  assert configFile["language"] == "python"
56
56
  assert configFile["database"]["hostname"] == "some host"
57
57
  assert configFile["database"]["port"] == 1234
@@ -68,11 +68,34 @@ def test_valid_config(mocker):
68
68
  assert "bob" not in os.environ
69
69
 
70
70
 
71
- def test_config_missing_params(mocker):
71
+ def test_valid_config_without_appdbname(mocker):
72
72
  mock_config = """
73
- name: "some app"
73
+ name: "some-app"
74
+ language: "python"
75
+ runtimeConfig:
76
+ start:
77
+ - "python3 main.py"
78
+ admin_port: 8001
74
79
  database:
75
80
  hostname: 'some host'
81
+ port: 1234
82
+ username: 'some user'
83
+ password: ${PGPASSWORD}
84
+ connectionTimeoutMillis: 3000
85
+ """
86
+ os.environ["BARBAR"] = "FOOFOO"
87
+ mocker.patch(
88
+ "builtins.open", side_effect=generate_mock_open(mock_filename, mock_config)
89
+ )
90
+
91
+ configFile = load_config(mock_filename)
92
+ assert configFile["database"]["app_db_name"] == "some_app"
93
+
94
+
95
+ def test_config_missing_params(mocker):
96
+ mock_config = """
97
+ name: "some-app"
98
+ database:
76
99
  port: 1234
77
100
  username: 'some user'
78
101
  password: abc123
@@ -85,12 +108,12 @@ def test_config_missing_params(mocker):
85
108
  with pytest.raises(DBOSInitializationError) as exc_info:
86
109
  load_config(mock_filename)
87
110
 
88
- assert "'app_db_name' is a required property" in str(exc_info.value)
111
+ assert "'hostname' is a required property" in str(exc_info.value)
89
112
 
90
113
 
91
114
  def test_config_extra_params(mocker):
92
115
  mock_config = """
93
- name: "some app"
116
+ name: "some-app"
94
117
  database:
95
118
  hostname: 'some host'
96
119
  port: 1234
@@ -136,7 +159,7 @@ def test_config_missing_name(mocker):
136
159
 
137
160
  def test_config_missing_language(mocker):
138
161
  mock_config = """
139
- name: "some app"
162
+ name: "some-app"
140
163
  database:
141
164
  hostname: 'some host'
142
165
  port: 1234
@@ -157,7 +180,7 @@ def test_config_missing_language(mocker):
157
180
 
158
181
  def test_config_bad_language(mocker):
159
182
  mock_config = """
160
- name: "some app"
183
+ name: "some-app"
161
184
  language: typescript
162
185
  database:
163
186
  hostname: 'some host'
@@ -177,10 +200,35 @@ def test_config_bad_language(mocker):
177
200
  assert "invalid language" in str(exc_info.value)
178
201
 
179
202
 
180
- def test_config_no_start(mocker):
203
+ def test_config_bad_name(mocker):
181
204
  mock_config = """
182
205
  name: "some app"
183
206
  language: python
207
+ runtimeConfig:
208
+ start:
209
+ - "python3 main.py"
210
+ database:
211
+ hostname: 'some host'
212
+ port: 1234
213
+ username: 'some user'
214
+ password: abc123
215
+ app_db_name: 'some db'
216
+ connectionTimeoutMillis: 3000
217
+ """
218
+ mocker.patch(
219
+ "builtins.open", side_effect=generate_mock_open(mock_filename, mock_config)
220
+ )
221
+
222
+ with pytest.raises(DBOSInitializationError) as exc_info:
223
+ load_config(mock_filename)
224
+
225
+ assert "Invalid app name" in str(exc_info.value)
226
+
227
+
228
+ def test_config_no_start(mocker):
229
+ mock_config = """
230
+ name: "some-app"
231
+ language: python
184
232
  database:
185
233
  hostname: 'some host'
186
234
  port: 1234
@@ -201,7 +249,7 @@ def test_config_no_start(mocker):
201
249
 
202
250
  def test_local_config(mocker):
203
251
  mock_config = """
204
- name: "some app"
252
+ name: "some-app"
205
253
  language: "python"
206
254
  runtimeConfig:
207
255
  start:
@@ -222,7 +270,7 @@ def test_local_config(mocker):
222
270
  )
223
271
 
224
272
  configFile = load_config(mock_filename)
225
- assert configFile["name"] == "some app"
273
+ assert configFile["name"] == "some-app"
226
274
  assert configFile["database"]["local_suffix"] == True
227
275
  assert configFile["language"] == "python"
228
276
  assert configFile["database"]["hostname"] == "some host"
@@ -231,3 +279,36 @@ def test_local_config(mocker):
231
279
  assert configFile["database"]["password"] == os.environ["PGPASSWORD"]
232
280
  assert configFile["database"]["app_db_name"] == "some_db_local"
233
281
  assert configFile["database"]["connectionTimeoutMillis"] == 3000
282
+
283
+
284
+ def test_local_config_without_name(mocker):
285
+ mock_config = """
286
+ name: "some-app"
287
+ language: "python"
288
+ runtimeConfig:
289
+ start:
290
+ - "python3 main.py"
291
+ admin_port: 8001
292
+ database:
293
+ hostname: 'some host'
294
+ port: 1234
295
+ username: 'some user'
296
+ password: ${PGPASSWORD}
297
+ connectionTimeoutMillis: 3000
298
+ local_suffix: true
299
+ """
300
+ os.environ["BARBAR"] = "FOOFOO"
301
+ mocker.patch(
302
+ "builtins.open", side_effect=generate_mock_open(mock_filename, mock_config)
303
+ )
304
+
305
+ configFile = load_config(mock_filename)
306
+ assert configFile["name"] == "some-app"
307
+ assert configFile["database"]["local_suffix"] == True
308
+ assert configFile["language"] == "python"
309
+ assert configFile["database"]["hostname"] == "some host"
310
+ assert configFile["database"]["port"] == 1234
311
+ assert configFile["database"]["username"] == "some user"
312
+ assert configFile["database"]["password"] == os.environ["PGPASSWORD"]
313
+ assert configFile["database"]["app_db_name"] == "some_app_local"
314
+ assert configFile["database"]["connectionTimeoutMillis"] == 3000
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import re
2
3
 
3
4
  import pytest
4
5
  import sqlalchemy as sa
@@ -94,5 +95,10 @@ def rollback_system_db(sysdb_url: str) -> None:
94
95
  )
95
96
  alembic_cfg = Config()
96
97
  alembic_cfg.set_main_option("script_location", migration_dir)
97
- alembic_cfg.set_main_option("sqlalchemy.url", sysdb_url)
98
+ escaped_conn_string = re.sub(
99
+ r"%(?=[0-9A-Fa-f]{2})",
100
+ "%%",
101
+ sysdb_url,
102
+ )
103
+ alembic_cfg.set_main_option("sqlalchemy.url", escaped_conn_string)
98
104
  command.downgrade(alembic_cfg, "base") # Rollback all migrations
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
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