phlo-postgres 0.1.0__tar.gz → 0.2.3__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 (29) hide show
  1. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/PKG-INFO +2 -1
  2. phlo_postgres-0.2.3/pyproject.toml +65 -0
  3. phlo_postgres-0.2.3/src/phlo_postgres/__init__.py +15 -0
  4. phlo_postgres-0.2.3/src/phlo_postgres/cli.py +283 -0
  5. phlo_postgres-0.2.3/src/phlo_postgres/cli_plugin.py +24 -0
  6. phlo_postgres-0.2.3/src/phlo_postgres/plugin.py +127 -0
  7. phlo_postgres-0.2.3/src/phlo_postgres/publish_target.py +21 -0
  8. phlo_postgres-0.2.3/src/phlo_postgres/resource.py +296 -0
  9. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres/service.yaml +3 -0
  10. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres/settings.py +12 -3
  11. phlo_postgres-0.2.3/src/phlo_postgres/volume_setup.yaml +20 -0
  12. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/PKG-INFO +2 -1
  13. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/SOURCES.txt +8 -1
  14. phlo_postgres-0.2.3/src/phlo_postgres.egg-info/entry_points.txt +10 -0
  15. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/requires.txt +1 -0
  16. phlo_postgres-0.2.3/tests/test_postgres_cli.py +211 -0
  17. phlo_postgres-0.2.3/tests/test_postgres_plugin.py +36 -0
  18. phlo_postgres-0.2.3/tests/test_resource.py +227 -0
  19. phlo_postgres-0.1.0/pyproject.toml +0 -42
  20. phlo_postgres-0.1.0/src/phlo_postgres/__init__.py +0 -7
  21. phlo_postgres-0.1.0/src/phlo_postgres/plugin.py +0 -47
  22. phlo_postgres-0.1.0/src/phlo_postgres.egg-info/entry_points.txt +0 -3
  23. phlo_postgres-0.1.0/tests/test_postgres_plugin.py +0 -11
  24. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/README.md +0 -0
  25. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/setup.cfg +0 -0
  26. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres/exporter_service.yaml +0 -0
  27. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/dependency_links.txt +0 -0
  28. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/top_level.txt +0 -0
  29. {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/tests/test_integration_postgres.py +0 -0
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phlo-postgres
3
- Version: 0.1.0
3
+ Version: 0.2.3
4
4
  Summary: Postgres service plugin for Phlo
5
5
  Author-email: Phlo Team <team@phlo.dev>
6
6
  License: MIT
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/plain
9
9
  Requires-Dist: phlo>=0.1.0
10
+ Requires-Dist: psycopg2-binary>=2.9.11
10
11
  Requires-Dist: pyyaml>=6.0.1
11
12
  Provides-Extra: dev
12
13
  Requires-Dist: pytest>=7.0; extra == "dev"
@@ -0,0 +1,65 @@
1
+ [build-system]
2
+ build-backend = "setuptools.build_meta"
3
+ requires = [
4
+ "setuptools>=45",
5
+ "wheel",
6
+ ]
7
+
8
+ [project]
9
+ dependencies = [
10
+ "phlo>=0.1.0",
11
+ "psycopg2-binary>=2.9.11",
12
+ "pyyaml>=6.0.1",
13
+ ]
14
+ description = "Postgres service plugin for Phlo"
15
+ name = "phlo-postgres"
16
+ requires-python = ">=3.11"
17
+ version = "0.2.3"
18
+
19
+ [[project.authors]]
20
+ email = "team@phlo.dev"
21
+ name = "Phlo Team"
22
+
23
+ [project.entry-points."phlo.plugins.cli"]
24
+ postgres = "phlo_postgres.cli_plugin:PostgresCliPlugin"
25
+
26
+ [project.entry-points."phlo.plugins.resources"]
27
+ postgres = "phlo_postgres.plugin:PostgresResourceProvider"
28
+
29
+ [project.entry-points."phlo.plugins.services"]
30
+ postgres = "phlo_postgres.plugin:PostgresServicePlugin"
31
+ postgres-exporter = "phlo_postgres.plugin:PostgresExporterServicePlugin"
32
+ postgres-volume-setup = "phlo_postgres.plugin:PostgresVolumeSetupServicePlugin"
33
+
34
+ [project.license]
35
+ text = "MIT"
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=7.0",
40
+ "ruff>=0.1.0",
41
+ ]
42
+
43
+ [project.readme]
44
+ content-type = "text/plain"
45
+ text = "Postgres service plugin for Phlo."
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+ target-version = "py311"
50
+
51
+ [tool.setuptools]
52
+ include-package-data = true
53
+
54
+ [tool.setuptools.package-data]
55
+ phlo_postgres = [
56
+ "service.yaml",
57
+ "exporter_service.yaml",
58
+ "volume_setup.yaml",
59
+ ]
60
+
61
+ [tool.setuptools.package-dir]
62
+ "" = "src"
63
+
64
+ [tool.setuptools.packages.find]
65
+ where = ["src"]
@@ -0,0 +1,15 @@
1
+ """Postgres service plugin package."""
2
+
3
+ from phlo_postgres.plugin import PostgresServicePlugin
4
+ from phlo_postgres.publish_target import PostgresPublishTarget
5
+ from phlo_postgres.resource import PostgresResource
6
+ from phlo_postgres.settings import PostgresSettings, get_settings
7
+
8
+ __all__ = [
9
+ "PostgresPublishTarget",
10
+ "PostgresResource",
11
+ "PostgresServicePlugin",
12
+ "PostgresSettings",
13
+ "get_settings",
14
+ ]
15
+ __version__ = "0.2.3"
@@ -0,0 +1,283 @@
1
+ """CLI commands for the PostgreSQL service."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import gzip
6
+ import subprocess
7
+ from pathlib import Path
8
+ from shutil import which
9
+ from subprocess import TimeoutExpired
10
+
11
+ import click
12
+
13
+ from phlo.cli.commands.services.utils import ensure_phlo_dir
14
+ from phlo.cli.infrastructure.command import CommandError, run_command
15
+ from phlo.cli.infrastructure.compose import compose_base_cmd
16
+ from phlo.cli.infrastructure.utils import get_project_name
17
+ from phlo_postgres.settings import get_settings
18
+
19
+
20
+ def _read_sql(*, query: str | None, file: Path | None) -> str:
21
+ """Return SQL text from inline query or file input."""
22
+ if query and file:
23
+ raise click.ClickException("Use either an inline query or --file, not both.")
24
+ if file is not None:
25
+ try:
26
+ sql = file.read_text(encoding="utf-8")
27
+ except OSError as exc:
28
+ raise click.ClickException(f"Failed to read SQL file: {file}") from exc
29
+ if sql.strip():
30
+ return sql
31
+ raise click.ClickException(f"SQL file is empty: {file}")
32
+ if query and query.strip():
33
+ return query
34
+ raise click.ClickException("Provide a SQL query argument or --file.")
35
+
36
+
37
+ def _require_docker() -> None:
38
+ """Validate that Docker CLI is installed."""
39
+ if which("docker") is None:
40
+ raise click.ClickException("docker command not found.")
41
+
42
+
43
+ def _postgres_exec_base(*, tty: bool) -> list[str]:
44
+ """Build the docker compose exec command for the Postgres container."""
45
+ phlo_dir = ensure_phlo_dir()
46
+ project_name = get_project_name()
47
+ cmd = compose_base_cmd(phlo_dir=phlo_dir, project_name=project_name)
48
+ cmd.append("exec")
49
+ if not tty:
50
+ cmd.append("-T")
51
+ cmd.append("postgres")
52
+ return cmd
53
+
54
+
55
+ def _postgres_identity(*, user: str | None, database: str | None) -> tuple[str, str]:
56
+ """Resolve effective Postgres user/database defaults."""
57
+ settings = get_settings()
58
+ return user or settings.postgres_user, database or settings.postgres_db
59
+
60
+
61
+ @click.command(
62
+ name="postgres",
63
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
64
+ )
65
+ @click.argument("postgres_args", nargs=-1, type=click.UNPROCESSED)
66
+ @click.pass_context
67
+ def postgres_group(ctx: click.Context, postgres_args: tuple[str, ...]) -> None:
68
+ """Run psql or Postgres helper commands against the project database."""
69
+ if postgres_args and postgres_args[0] == "query":
70
+ postgres_query.main(
71
+ args=list(postgres_args[1:]),
72
+ prog_name="phlo postgres query",
73
+ standalone_mode=False,
74
+ )
75
+ return
76
+ if postgres_args and postgres_args[0] == "dump":
77
+ postgres_dump.main(
78
+ args=list(postgres_args[1:]),
79
+ prog_name="phlo postgres dump",
80
+ standalone_mode=False,
81
+ )
82
+ return
83
+ if postgres_args and postgres_args[0] == "restore":
84
+ postgres_restore.main(
85
+ args=list(postgres_args[1:]),
86
+ prog_name="phlo postgres restore",
87
+ standalone_mode=False,
88
+ )
89
+ return
90
+ if postgres_args and postgres_args[0] == "vacuum":
91
+ postgres_vacuum.main(
92
+ args=list(postgres_args[1:]),
93
+ prog_name="phlo postgres vacuum",
94
+ standalone_mode=False,
95
+ )
96
+ return
97
+
98
+ _require_docker()
99
+ user, database = _postgres_identity(user=None, database=None)
100
+ cmd = _postgres_exec_base(tty=True)
101
+ cmd.extend(["psql", "-U", user, "-d", database])
102
+ cmd.extend(postgres_args)
103
+ result = subprocess.run(cmd, check=False)
104
+ if result.returncode != 0:
105
+ raise click.ClickException(f"psql exited with status {result.returncode}.")
106
+
107
+
108
+ @click.command(name="query")
109
+ @click.argument("query", required=False)
110
+ @click.option(
111
+ "--file",
112
+ "query_file",
113
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
114
+ )
115
+ @click.option("--user", default=None, help="Database user.")
116
+ @click.option("--db", "database", default=None, help="Database name.")
117
+ @click.option("--timeout", "timeout_seconds", default=30, show_default=True, type=int)
118
+ def postgres_query(
119
+ query: str | None,
120
+ query_file: Path | None,
121
+ user: str | None,
122
+ database: str | None,
123
+ timeout_seconds: int,
124
+ ) -> None:
125
+ """Execute a SQL query against the running PostgreSQL service."""
126
+ _require_docker()
127
+ sql = _read_sql(query=query, file=query_file)
128
+ resolved_user, resolved_db = _postgres_identity(user=user, database=database)
129
+ cmd = _postgres_exec_base(tty=False)
130
+ cmd.extend(["psql", "-U", resolved_user, "-d", resolved_db, "-v", "ON_ERROR_STOP=1", "-c", sql])
131
+
132
+ try:
133
+ result = run_command(
134
+ cmd,
135
+ timeout_seconds=timeout_seconds,
136
+ capture_output=True,
137
+ check=True,
138
+ )
139
+ except CommandError as exc:
140
+ stderr = exc.stderr.strip()
141
+ raise click.ClickException(stderr or str(exc)) from exc
142
+ except TimeoutExpired as exc:
143
+ raise click.ClickException(f"Query timed out after {timeout_seconds} seconds.") from exc
144
+
145
+ if result.stdout:
146
+ click.echo(result.stdout, nl=False)
147
+
148
+
149
+ @click.command(name="dump")
150
+ @click.option(
151
+ "--file",
152
+ "output_file",
153
+ type=click.Path(dir_okay=False, path_type=Path),
154
+ required=True,
155
+ help="Output path. Use .gz for gzip-compressed dumps.",
156
+ )
157
+ @click.option("--user", default=None, help="Database user.")
158
+ @click.option("--db", "database", default=None, help="Database name.")
159
+ @click.option("--timeout", "timeout_seconds", default=120, show_default=True, type=int)
160
+ def postgres_dump(
161
+ output_file: Path,
162
+ user: str | None,
163
+ database: str | None,
164
+ timeout_seconds: int,
165
+ ) -> None:
166
+ """Write a PostgreSQL logical backup to a local file."""
167
+ _require_docker()
168
+ resolved_user, resolved_db = _postgres_identity(user=user, database=database)
169
+ cmd = _postgres_exec_base(tty=False)
170
+ cmd.extend(["pg_dump", "-U", resolved_user, resolved_db])
171
+
172
+ try:
173
+ result = run_command(
174
+ cmd,
175
+ timeout_seconds=timeout_seconds,
176
+ capture_output=True,
177
+ check=True,
178
+ )
179
+ except CommandError as exc:
180
+ stderr = exc.stderr.strip()
181
+ raise click.ClickException(stderr or str(exc)) from exc
182
+ except TimeoutExpired as exc:
183
+ raise click.ClickException(f"Dump timed out after {timeout_seconds} seconds.") from exc
184
+
185
+ output_file.parent.mkdir(parents=True, exist_ok=True)
186
+ try:
187
+ if output_file.suffix == ".gz":
188
+ with gzip.open(output_file, "wt", encoding="utf-8") as handle:
189
+ handle.write(result.stdout)
190
+ else:
191
+ output_file.write_text(result.stdout, encoding="utf-8")
192
+ except OSError as exc:
193
+ raise click.ClickException(f"Failed to write dump file: {output_file}") from exc
194
+
195
+ click.echo(f"Wrote dump to {output_file}")
196
+
197
+
198
+ @click.command(name="restore")
199
+ @click.option(
200
+ "--file",
201
+ "input_file",
202
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
203
+ required=True,
204
+ help="Input dump file. Supports .sql and .gz files.",
205
+ )
206
+ @click.option("--user", default=None, help="Database user.")
207
+ @click.option("--db", "database", default=None, help="Database name.")
208
+ @click.option("--timeout", "timeout_seconds", default=120, show_default=True, type=int)
209
+ def postgres_restore(
210
+ input_file: Path,
211
+ user: str | None,
212
+ database: str | None,
213
+ timeout_seconds: int,
214
+ ) -> None:
215
+ """Restore a PostgreSQL logical backup from a local file."""
216
+ _require_docker()
217
+ resolved_user, resolved_db = _postgres_identity(user=user, database=database)
218
+ cmd = _postgres_exec_base(tty=False)
219
+ cmd.extend(["psql", "-U", resolved_user, "-d", resolved_db, "-v", "ON_ERROR_STOP=1"])
220
+
221
+ try:
222
+ if input_file.suffix == ".gz":
223
+ with gzip.open(input_file, "rt", encoding="utf-8") as handle:
224
+ sql = handle.read()
225
+ else:
226
+ sql = input_file.read_text(encoding="utf-8")
227
+ except OSError as exc:
228
+ raise click.ClickException(f"Failed to read restore file: {input_file}") from exc
229
+
230
+ try:
231
+ result = subprocess.run(
232
+ cmd,
233
+ input=sql,
234
+ text=True,
235
+ capture_output=True,
236
+ timeout=timeout_seconds,
237
+ check=False,
238
+ )
239
+ except TimeoutExpired as exc:
240
+ raise click.ClickException(f"Restore timed out after {timeout_seconds} seconds.") from exc
241
+
242
+ if result.returncode != 0:
243
+ stderr = (result.stderr or "").strip()
244
+ raise click.ClickException(stderr or f"Restore failed with status {result.returncode}.")
245
+
246
+ click.echo(f"Restored {input_file} into {resolved_db}")
247
+
248
+
249
+ @click.command(name="vacuum")
250
+ @click.option("--user", default=None, help="Database user.")
251
+ @click.option("--db", "database", default=None, help="Database name.")
252
+ @click.option("--analyze/--no-analyze", default=True, show_default=True)
253
+ @click.option("--timeout", "timeout_seconds", default=120, show_default=True, type=int)
254
+ def postgres_vacuum(
255
+ user: str | None,
256
+ database: str | None,
257
+ analyze: bool,
258
+ timeout_seconds: int,
259
+ ) -> None:
260
+ """Run vacuumdb inside the PostgreSQL service container."""
261
+ _require_docker()
262
+ resolved_user, resolved_db = _postgres_identity(user=user, database=database)
263
+ cmd = _postgres_exec_base(tty=False)
264
+ cmd.extend(["vacuumdb", "-U", resolved_user])
265
+ if analyze:
266
+ cmd.append("-z")
267
+ cmd.append(resolved_db)
268
+
269
+ try:
270
+ result = run_command(
271
+ cmd,
272
+ timeout_seconds=timeout_seconds,
273
+ capture_output=True,
274
+ check=True,
275
+ )
276
+ except CommandError as exc:
277
+ stderr = exc.stderr.strip()
278
+ raise click.ClickException(stderr or str(exc)) from exc
279
+ except TimeoutExpired as exc:
280
+ raise click.ClickException(f"Vacuum timed out after {timeout_seconds} seconds.") from exc
281
+
282
+ if result.stdout:
283
+ click.echo(result.stdout, nl=False)
@@ -0,0 +1,24 @@
1
+ """CLI plugin for PostgreSQL commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ from phlo.plugins.base import CliCommandPlugin, PluginMetadata
8
+
9
+ from phlo_postgres.cli import postgres_group
10
+
11
+
12
+ class PostgresCliPlugin(CliCommandPlugin):
13
+ """Register PostgreSQL CLI commands."""
14
+
15
+ @property
16
+ def metadata(self) -> PluginMetadata:
17
+ return PluginMetadata(
18
+ name="postgres",
19
+ version="0.1.0",
20
+ description="CLI commands for PostgreSQL service access",
21
+ )
22
+
23
+ def get_cli_commands(self) -> list[click.Command]:
24
+ return [postgres_group]
@@ -0,0 +1,127 @@
1
+ """Postgres service plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import resources
6
+ from typing import Any
7
+
8
+ import yaml
9
+ from phlo.capabilities import PublishTargetSpec, ResourceSpec
10
+ from phlo.plugins import PluginMetadata, ResourceProviderPlugin, ServicePlugin
11
+
12
+ from phlo_postgres.publish_target import PostgresPublishTarget
13
+ from phlo_postgres.resource import PostgresResource
14
+
15
+
16
+ class PostgresServicePlugin(ServicePlugin):
17
+ """Service plugin for Postgres."""
18
+
19
+ @property
20
+ def metadata(self) -> PluginMetadata:
21
+ """Return plugin metadata for the Postgres service.
22
+
23
+ Returns:
24
+ PluginMetadata: Metadata describing the service plugin.
25
+ """
26
+ return PluginMetadata(
27
+ name="postgres",
28
+ version="0.1.0",
29
+ description="PostgreSQL database for metadata and operational storage",
30
+ author="Phlo Team",
31
+ tags=["core", "database", "postgres"],
32
+ )
33
+
34
+ @property
35
+ def service_definition(self) -> dict[str, Any]:
36
+ """Load the Postgres service definition from package data.
37
+
38
+ Returns:
39
+ dict[str, Any]: Parsed Docker Compose service definition.
40
+ """
41
+ service_path = resources.files("phlo_postgres").joinpath("service.yaml")
42
+ return yaml.safe_load(service_path.read_text(encoding="utf-8"))
43
+
44
+
45
+ class PostgresExporterServicePlugin(ServicePlugin):
46
+ """Service plugin for Postgres Prometheus exporter."""
47
+
48
+ @property
49
+ def metadata(self) -> PluginMetadata:
50
+ """Return plugin metadata for the Postgres exporter service.
51
+
52
+ Returns:
53
+ PluginMetadata: Metadata describing the exporter plugin.
54
+ """
55
+ return PluginMetadata(
56
+ name="postgres-exporter",
57
+ version="0.1.0",
58
+ description="Prometheus exporter for PostgreSQL metrics",
59
+ author="Phlo Team",
60
+ tags=["observability", "metrics", "postgres"],
61
+ )
62
+
63
+ @property
64
+ def service_definition(self) -> dict[str, Any]:
65
+ """Load the Postgres exporter service definition from package data.
66
+
67
+ Returns:
68
+ dict[str, Any]: Parsed Docker Compose service definition.
69
+ """
70
+ service_path = resources.files("phlo_postgres").joinpath("exporter_service.yaml")
71
+ return yaml.safe_load(service_path.read_text(encoding="utf-8"))
72
+
73
+
74
+ class PostgresVolumeSetupServicePlugin(ServicePlugin):
75
+ """Service plugin for PostgreSQL bind-mount ownership bootstrap."""
76
+
77
+ @property
78
+ def metadata(self) -> PluginMetadata:
79
+ """Return plugin metadata for the Postgres volume setup service."""
80
+ return PluginMetadata(
81
+ name="postgres-volume-setup",
82
+ version="0.1.0",
83
+ description="Initialize PostgreSQL data volume permissions",
84
+ author="Phlo Team",
85
+ tags=["core", "database", "postgres"],
86
+ )
87
+
88
+ @property
89
+ def service_definition(self) -> dict[str, Any]:
90
+ """Load the Postgres volume setup definition from package data."""
91
+ service_path = resources.files("phlo_postgres").joinpath("volume_setup.yaml")
92
+ return yaml.safe_load(service_path.read_text(encoding="utf-8"))
93
+
94
+
95
+ class PostgresResourceProvider(ResourceProviderPlugin):
96
+ """Resource provider plugin that exposes the Postgres resource."""
97
+
98
+ @property
99
+ def metadata(self) -> PluginMetadata:
100
+ """Return plugin metadata for the Postgres resource provider.
101
+
102
+ Returns:
103
+ PluginMetadata: Metadata describing the resource provider plugin.
104
+ """
105
+ return PluginMetadata(
106
+ name="postgres",
107
+ version="0.1.0",
108
+ description="Postgres resource for Phlo",
109
+ )
110
+
111
+ def get_resources(self) -> list[ResourceSpec]:
112
+ """Return resource specifications exposed by this provider.
113
+
114
+ Returns:
115
+ list[ResourceSpec]: Registered resource specifications.
116
+ """
117
+ return [ResourceSpec(name="postgres", resource=PostgresResource())]
118
+
119
+ def get_publish_targets(self) -> list[PublishTargetSpec]:
120
+ """Return publish target capability specs exposed by this provider."""
121
+ return [
122
+ PublishTargetSpec(
123
+ name="postgres",
124
+ provider=PostgresPublishTarget(),
125
+ metadata={"target_system": "postgres", "role": "serving"},
126
+ )
127
+ ]
@@ -0,0 +1,21 @@
1
+ """Publish target wrapper for Postgres serving outputs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+ from phlo_postgres.resource import PostgresResource
8
+ from phlo_postgres.settings import get_settings
9
+
10
+
11
+ @dataclass
12
+ class PostgresPublishTarget:
13
+ """Structured publish target for Postgres serving tables."""
14
+
15
+ resource: PostgresResource = field(default_factory=PostgresResource)
16
+ target_system: str = "postgres"
17
+
18
+ @property
19
+ def default_schema(self) -> str:
20
+ """Return the default serving schema for published mart tables."""
21
+ return get_settings().postgres_mart_schema