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.
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/PKG-INFO +2 -1
- phlo_postgres-0.2.3/pyproject.toml +65 -0
- phlo_postgres-0.2.3/src/phlo_postgres/__init__.py +15 -0
- phlo_postgres-0.2.3/src/phlo_postgres/cli.py +283 -0
- phlo_postgres-0.2.3/src/phlo_postgres/cli_plugin.py +24 -0
- phlo_postgres-0.2.3/src/phlo_postgres/plugin.py +127 -0
- phlo_postgres-0.2.3/src/phlo_postgres/publish_target.py +21 -0
- phlo_postgres-0.2.3/src/phlo_postgres/resource.py +296 -0
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres/service.yaml +3 -0
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres/settings.py +12 -3
- phlo_postgres-0.2.3/src/phlo_postgres/volume_setup.yaml +20 -0
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/PKG-INFO +2 -1
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/SOURCES.txt +8 -1
- phlo_postgres-0.2.3/src/phlo_postgres.egg-info/entry_points.txt +10 -0
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/requires.txt +1 -0
- phlo_postgres-0.2.3/tests/test_postgres_cli.py +211 -0
- phlo_postgres-0.2.3/tests/test_postgres_plugin.py +36 -0
- phlo_postgres-0.2.3/tests/test_resource.py +227 -0
- phlo_postgres-0.1.0/pyproject.toml +0 -42
- phlo_postgres-0.1.0/src/phlo_postgres/__init__.py +0 -7
- phlo_postgres-0.1.0/src/phlo_postgres/plugin.py +0 -47
- phlo_postgres-0.1.0/src/phlo_postgres.egg-info/entry_points.txt +0 -3
- phlo_postgres-0.1.0/tests/test_postgres_plugin.py +0 -11
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/README.md +0 -0
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/setup.cfg +0 -0
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres/exporter_service.yaml +0 -0
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/dependency_links.txt +0 -0
- {phlo_postgres-0.1.0 → phlo_postgres-0.2.3}/src/phlo_postgres.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|