phlo-postgres 0.2.3__tar.gz → 0.3.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 (32) hide show
  1. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/PKG-INFO +1 -1
  2. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/pyproject.toml +1 -1
  3. phlo_postgres-0.3.0/src/phlo_postgres/__init__.py +30 -0
  4. phlo_postgres-0.3.0/src/phlo_postgres/authorization.py +173 -0
  5. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres/cli.py +184 -20
  6. phlo_postgres-0.3.0/src/phlo_postgres/cli_plugin.py +88 -0
  7. phlo_postgres-0.3.0/src/phlo_postgres/plugin.py +213 -0
  8. phlo_postgres-0.3.0/src/phlo_postgres/publish_target.py +85 -0
  9. phlo_postgres-0.3.0/src/phlo_postgres/resource.py +583 -0
  10. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres/service.yaml +1 -2
  11. phlo_postgres-0.3.0/src/phlo_postgres/settings.py +152 -0
  12. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres/volume_setup.yaml +1 -1
  13. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres.egg-info/PKG-INFO +1 -1
  14. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres.egg-info/SOURCES.txt +2 -0
  15. phlo_postgres-0.3.0/tests/test_authorization.py +168 -0
  16. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/tests/test_postgres_cli.py +6 -6
  17. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/tests/test_resource.py +1 -0
  18. phlo_postgres-0.2.3/src/phlo_postgres/__init__.py +0 -15
  19. phlo_postgres-0.2.3/src/phlo_postgres/cli_plugin.py +0 -24
  20. phlo_postgres-0.2.3/src/phlo_postgres/plugin.py +0 -127
  21. phlo_postgres-0.2.3/src/phlo_postgres/publish_target.py +0 -21
  22. phlo_postgres-0.2.3/src/phlo_postgres/resource.py +0 -296
  23. phlo_postgres-0.2.3/src/phlo_postgres/settings.py +0 -43
  24. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/README.md +0 -0
  25. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/setup.cfg +0 -0
  26. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres/exporter_service.yaml +0 -0
  27. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres.egg-info/dependency_links.txt +0 -0
  28. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres.egg-info/entry_points.txt +0 -0
  29. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres.egg-info/requires.txt +0 -0
  30. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/src/phlo_postgres.egg-info/top_level.txt +0 -0
  31. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/tests/test_integration_postgres.py +0 -0
  32. {phlo_postgres-0.2.3 → phlo_postgres-0.3.0}/tests/test_postgres_plugin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phlo-postgres
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Postgres service plugin for Phlo
5
5
  Author-email: Phlo Team <team@phlo.dev>
6
6
  License: MIT
@@ -14,7 +14,7 @@ dependencies = [
14
14
  description = "Postgres service plugin for Phlo"
15
15
  name = "phlo-postgres"
16
16
  requires-python = ">=3.11"
17
- version = "0.2.3"
17
+ version = "0.3.0"
18
18
 
19
19
  [[project.authors]]
20
20
  email = "team@phlo.dev"
@@ -0,0 +1,30 @@
1
+ """Phlo PostgreSQL metadata store package.
2
+
3
+ This package provides the PostgreSQL integration for Phlo, including:
4
+ - Service plugin for managing PostgreSQL containers
5
+ - Resource management with connection pooling
6
+ - CLI commands for database operations
7
+ - Configuration settings management
8
+ - Publish targets for serving data
9
+
10
+ Example:
11
+ >>> from phlo_postgres import PostgresResource, get_settings
12
+ >>> settings = get_settings()
13
+ >>> with PostgresResource() as db:
14
+ ... rows = db.query("SELECT * FROM users LIMIT 10")
15
+
16
+ """
17
+
18
+ from phlo_postgres.plugin import PostgresServicePlugin
19
+ from phlo_postgres.publish_target import PostgresPublishTarget
20
+ from phlo_postgres.resource import PostgresResource
21
+ from phlo_postgres.settings import PostgresSettings, get_settings
22
+
23
+ __all__ = [
24
+ "PostgresPublishTarget",
25
+ "PostgresResource",
26
+ "PostgresServicePlugin",
27
+ "PostgresSettings",
28
+ "get_settings",
29
+ ]
30
+ __version__ = "0.3.0"
@@ -0,0 +1,173 @@
1
+ """CLI regulated surface adapter for PostgreSQL.
2
+
3
+ This module provides the regulated surface adapter for the PostgreSQL CLI,
4
+ declaring mutation commands that require authorization and enforcing
5
+ through core enforcement.
6
+
7
+ Commands:
8
+ - postgres query: Execute SQL (mutation - can be SELECT or DDL/DML)
9
+ - postgres dump: Create database dump (mutation)
10
+ - postgres restore: Restore from dump (mutation)
11
+ - postgres vacuum: Run vacuumdb maintenance (mutation)
12
+ - postgres: Raw psql passthrough (mutation)
13
+
14
+ Resource/Action mapping:
15
+ - dataset.query: SQL execution
16
+ - dataset.manage: Database maintenance operations
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import os
22
+ import threading
23
+ from typing import Any
24
+
25
+ from phlo.cli.authorization import CliPrincipalResolver
26
+ from phlo.logging import get_logger
27
+ from phlo.security.adapters import (
28
+ EnforcementResult,
29
+ SurfaceOperation,
30
+ )
31
+ from phlo.security.enforcement import enforce
32
+
33
+ logger = get_logger(__name__)
34
+
35
+ SURFACE_NAME = "phlo-postgres-cli"
36
+ FRAMEWORK_TYPE = "cli"
37
+
38
+ MUTATION_COMMANDS: frozenset[str] = frozenset(
39
+ {
40
+ "postgres.query",
41
+ "postgres.dump",
42
+ "postgres.restore",
43
+ "postgres.vacuum",
44
+ "postgres",
45
+ }
46
+ )
47
+
48
+ READ_COMMANDS: frozenset[str] = frozenset({})
49
+
50
+ COMMAND_RESOURCE_MAP: dict[str, str] = {
51
+ "postgres.query": "dataset",
52
+ "postgres.dump": "dataset",
53
+ "postgres.restore": "dataset",
54
+ "postgres.vacuum": "dataset",
55
+ "postgres": "dataset",
56
+ }
57
+
58
+ COMMAND_ACTION_MAP: dict[str, str] = {
59
+ "postgres.query": "dataset.query",
60
+ "postgres.dump": "dataset.manage",
61
+ "postgres.restore": "dataset.manage",
62
+ "postgres.vacuum": "dataset.manage",
63
+ "postgres": "dataset.query",
64
+ }
65
+
66
+
67
+ class PostgresCliSurfaceAdapter:
68
+ """Regulated surface adapter for PostgreSQL CLI."""
69
+
70
+ _instance: PostgresCliSurfaceAdapter | None = None
71
+ _lock = threading.Lock()
72
+
73
+ def __init__(self) -> None:
74
+ self._resolver = CliPrincipalResolver()
75
+
76
+ @classmethod
77
+ def get_instance(cls) -> PostgresCliSurfaceAdapter:
78
+ if cls._instance is None:
79
+ with cls._lock:
80
+ if cls._instance is None:
81
+ cls._instance = cls()
82
+ return cls._instance
83
+
84
+ @property
85
+ def surface_name(self) -> str:
86
+ return SURFACE_NAME
87
+
88
+ @property
89
+ def framework_type(self) -> str:
90
+ return FRAMEWORK_TYPE
91
+
92
+ def list_operations(self) -> list[SurfaceOperation]:
93
+ operations: list[SurfaceOperation] = []
94
+ for command in MUTATION_COMMANDS:
95
+ resource_type = COMMAND_RESOURCE_MAP.get(command, "dataset")
96
+ action = COMMAND_ACTION_MAP.get(command, "dataset.query")
97
+ operations.append(
98
+ SurfaceOperation(
99
+ action=action,
100
+ resource_type=resource_type,
101
+ operation_name=command,
102
+ resource_id_strategy=None,
103
+ framework_metadata={"command": command},
104
+ )
105
+ )
106
+ return operations
107
+
108
+ def is_active(self, runtime: Any) -> bool:
109
+ return True
110
+
111
+ def install(self, runtime: Any) -> None:
112
+ pass
113
+
114
+ def enforce_mutation(self, command: str, resource_id: str | None = None) -> EnforcementResult:
115
+ """Enforce authorization for a mutation command."""
116
+ if command not in MUTATION_COMMANDS:
117
+ return EnforcementResult.allow()
118
+
119
+ action = COMMAND_ACTION_MAP.get(command, "dataset.query")
120
+ resource_type = COMMAND_RESOURCE_MAP.get(command, "dataset")
121
+ resource_id_final = resource_id or f"postgres:{command}"
122
+
123
+ principal = self._resolver.resolve()
124
+
125
+ from phlo.capabilities.interfaces import ResourceRef
126
+
127
+ resource = ResourceRef(
128
+ resource_type=resource_type,
129
+ resource_id=resource_id_final,
130
+ )
131
+
132
+ request_id = os.environ.get("PHLO_REQUEST_ID")
133
+
134
+ result = enforce(
135
+ principal=principal,
136
+ action=action,
137
+ resource=resource,
138
+ context=None,
139
+ request_id=request_id,
140
+ surface=SURFACE_NAME,
141
+ )
142
+
143
+ logger.debug(
144
+ "postgres_cli_mutation_enforcement_result",
145
+ command=command,
146
+ action=action,
147
+ result=result.variant,
148
+ subject=principal.subject,
149
+ )
150
+
151
+ return result
152
+
153
+ def check_command_authorization(self, command_path: str) -> EnforcementResult:
154
+ """Check if a command is authorized to run."""
155
+ if command_path in READ_COMMANDS:
156
+ return EnforcementResult.allow()
157
+
158
+ if command_path in MUTATION_COMMANDS:
159
+ return self.enforce_mutation(command_path)
160
+
161
+ logger.warning(
162
+ "postgres_cli_unknown_command_classification",
163
+ command=command_path,
164
+ )
165
+ return EnforcementResult.deny(
166
+ reason_code="unknown_command",
167
+ explanation=f"Command '{command_path}' is not classified as read or mutation",
168
+ )
169
+
170
+
171
+ def get_postgres_cli_adapter() -> PostgresCliSurfaceAdapter:
172
+ """Get the singleton PostgreSQL CLI surface adapter."""
173
+ return PostgresCliSurfaceAdapter.get_instance()
@@ -1,16 +1,32 @@
1
- """CLI commands for the PostgreSQL service."""
1
+ """CLI commands for PostgreSQL service management.
2
+
3
+ This module provides Click-based CLI commands for interacting with the PostgreSQL
4
+ service, including running queries, dumping/restoring databases, and performing
5
+ maintenance operations like vacuuming.
6
+
7
+ All commands execute within the PostgreSQL container backend container via docker compose exec.
8
+
9
+ Example:
10
+ $ phlo postgres query "SELECT * FROM users LIMIT 10"
11
+ $ phlo postgres dump --file backup.sql.gz
12
+ $ phlo postgres restore --file backup.sql.gz
13
+ $ phlo postgres vacuum --analyze
14
+
15
+ """
2
16
 
3
17
  from __future__ import annotations
4
18
 
5
19
  import gzip
6
20
  import subprocess
7
21
  from pathlib import Path
8
- from shutil import which
9
22
  from subprocess import TimeoutExpired
10
23
 
11
24
  import click
12
25
 
13
- from phlo.cli.commands.services.utils import ensure_phlo_dir
26
+ from phlo.cli.commands.services.utils import (
27
+ ensure_phlo_dir,
28
+ require_container_backend as _require_selected_container_backend,
29
+ )
14
30
  from phlo.cli.infrastructure.command import CommandError, run_command
15
31
  from phlo.cli.infrastructure.compose import compose_base_cmd
16
32
  from phlo.cli.infrastructure.utils import get_project_name
@@ -18,7 +34,27 @@ from phlo_postgres.settings import get_settings
18
34
 
19
35
 
20
36
  def _read_sql(*, query: str | None, file: Path | None) -> str:
21
- """Return SQL text from inline query or file input."""
37
+ """Read SQL from inline query string or file path.
38
+
39
+ Validates that exactly one of query or file is provided and returns the
40
+ SQL content. Handles empty file detection and encoding issues.
41
+
42
+ Args:
43
+ query: Inline SQL query string.
44
+ file: Path to SQL file to read.
45
+
46
+ Returns:
47
+ str: The SQL content to execute.
48
+
49
+ Raises:
50
+ click.ClickException: If both query and file are provided, neither is
51
+ provided, or the file is empty/cannot be read.
52
+
53
+ Example:
54
+ >>> sql = _read_sql(query="SELECT 1")
55
+ >>> sql = _read_sql(file=Path("query.sql"))
56
+
57
+ """
22
58
  if query and file:
23
59
  raise click.ClickException("Use either an inline query or --file, not both.")
24
60
  if file is not None:
@@ -34,14 +70,29 @@ def _read_sql(*, query: str | None, file: Path | None) -> str:
34
70
  raise click.ClickException("Provide a SQL query argument or --file.")
35
71
 
36
72
 
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.")
73
+ def _require_container_backend() -> None:
74
+ """Validate that the selected container backend is available."""
75
+ _require_selected_container_backend()
41
76
 
42
77
 
43
78
  def _postgres_exec_base(*, tty: bool) -> list[str]:
44
- """Build the docker compose exec command for the Postgres container."""
79
+ """Build the docker compose exec base command for PostgreSQL container.
80
+
81
+ Constructs the initial portion of the docker compose exec command including
82
+ project name and service name. Used as a base for all container operations.
83
+
84
+ Args:
85
+ tty: Whether to allocate a TTY (-t flag). Disable for non-interactive
86
+ commands that capture output.
87
+
88
+ Returns:
89
+ list[str]: Base command as a list of strings ready for subprocess.
90
+
91
+ Example:
92
+ >>> cmd = _postgres_exec_base(tty=True)
93
+ >>> # Returns: ['docker', 'compose', '-p', 'phlo', '-f', '...', 'exec', '-t', 'postgres']
94
+
95
+ """
45
96
  phlo_dir = ensure_phlo_dir()
46
97
  project_name = get_project_name()
47
98
  cmd = compose_base_cmd(phlo_dir=phlo_dir, project_name=project_name)
@@ -53,7 +104,26 @@ def _postgres_exec_base(*, tty: bool) -> list[str]:
53
104
 
54
105
 
55
106
  def _postgres_identity(*, user: str | None, database: str | None) -> tuple[str, str]:
56
- """Resolve effective Postgres user/database defaults."""
107
+ """Resolve PostgreSQL connection identity (user and database).
108
+
109
+ Returns explicit values if provided, otherwise falls back to settings
110
+ defaults. This allows CLI commands to use configured defaults while
111
+ permitting overrides.
112
+
113
+ Args:
114
+ user: Database username override, or None to use settings default.
115
+ database: Database name override, or None to use settings default.
116
+
117
+ Returns:
118
+ tuple[str, str]: Tuple of (resolved_user, resolved_database).
119
+
120
+ Example:
121
+ >>> user, db = _postgres_identity(user=None, database=None)
122
+ >>> # Uses settings.postgres_user and settings.postgres_db
123
+ >>> user, db = _postgres_identity(user="admin", database=None)
124
+ >>> # Uses "admin" for user, settings default for database
125
+
126
+ """
57
127
  settings = get_settings()
58
128
  return user or settings.postgres_user, database or settings.postgres_db
59
129
 
@@ -65,7 +135,24 @@ def _postgres_identity(*, user: str | None, database: str | None) -> tuple[str,
65
135
  @click.argument("postgres_args", nargs=-1, type=click.UNPROCESSED)
66
136
  @click.pass_context
67
137
  def postgres_group(ctx: click.Context, postgres_args: tuple[str, ...]) -> None:
68
- """Run psql or Postgres helper commands against the project database."""
138
+ """Run psql or PostgreSQL helper commands against the project database.
139
+
140
+ This is the main entry point for PostgreSQL CLI operations. It supports:
141
+ - Interactive psql sessions (default if no subcommand)
142
+ - Subcommands: query, dump, restore, vacuum
143
+ - Direct psql arguments passthrough
144
+
145
+ Args:
146
+ ctx: Click context object.
147
+ postgres_args: Additional arguments passed to psql or subcommands.
148
+
149
+ Example:
150
+ $ phlo postgres # Interactive psql
151
+ $ phlo postgres -c "SELECT 1" # One-off query via psql
152
+ $ phlo postgres query "SELECT * FROM users"
153
+ $ phlo postgres dump --file backup.sql.gz
154
+
155
+ """
69
156
  if postgres_args and postgres_args[0] == "query":
70
157
  postgres_query.main(
71
158
  args=list(postgres_args[1:]),
@@ -95,7 +182,7 @@ def postgres_group(ctx: click.Context, postgres_args: tuple[str, ...]) -> None:
95
182
  )
96
183
  return
97
184
 
98
- _require_docker()
185
+ _require_container_backend()
99
186
  user, database = _postgres_identity(user=None, database=None)
100
187
  cmd = _postgres_exec_base(tty=True)
101
188
  cmd.extend(["psql", "-U", user, "-d", database])
@@ -122,8 +209,27 @@ def postgres_query(
122
209
  database: str | None,
123
210
  timeout_seconds: int,
124
211
  ) -> None:
125
- """Execute a SQL query against the running PostgreSQL service."""
126
- _require_docker()
212
+ """Execute a SQL query against the running PostgreSQL service.
213
+
214
+ Executes a SQL query inside the PostgreSQL container and prints results
215
+ to stdout. Supports inline queries or reading from a file.
216
+
217
+ Args:
218
+ query: SQL query string to execute.
219
+ query_file: Path to file containing SQL query.
220
+ user: Database user (default from settings).
221
+ database: Database name (default from settings).
222
+ timeout_seconds: Maximum time to wait for query completion.
223
+
224
+ Raises:
225
+ click.ClickException: If the query fails or times out.
226
+
227
+ Example:
228
+ $ phlo postgres query "SELECT * FROM users"
229
+ $ phlo postgres query --file query.sql --timeout 60
230
+
231
+ """
232
+ _require_container_backend()
127
233
  sql = _read_sql(query=query, file=query_file)
128
234
  resolved_user, resolved_db = _postgres_identity(user=user, database=database)
129
235
  cmd = _postgres_exec_base(tty=False)
@@ -163,8 +269,26 @@ def postgres_dump(
163
269
  database: str | None,
164
270
  timeout_seconds: int,
165
271
  ) -> None:
166
- """Write a PostgreSQL logical backup to a local file."""
167
- _require_docker()
272
+ """Create a PostgreSQL logical backup (pg_dump) to a local file.
273
+
274
+ Dumps the entire database using pg_dump, with optional gzip compression
275
+ if the output file has a .gz extension.
276
+
277
+ Args:
278
+ output_file: Path to write the dump. Use .gz extension for compression.
279
+ user: Database user (default from settings).
280
+ database: Database name (default from settings).
281
+ timeout_seconds: Maximum time to wait for dump completion.
282
+
283
+ Raises:
284
+ click.ClickException: If the dump fails or times out.
285
+
286
+ Example:
287
+ $ phlo postgres dump --file backup.sql
288
+ $ phlo postgres dump --file backup.sql.gz --timeout 300
289
+
290
+ """
291
+ _require_container_backend()
168
292
  resolved_user, resolved_db = _postgres_identity(user=user, database=database)
169
293
  cmd = _postgres_exec_base(tty=False)
170
294
  cmd.extend(["pg_dump", "-U", resolved_user, resolved_db])
@@ -212,8 +336,29 @@ def postgres_restore(
212
336
  database: str | None,
213
337
  timeout_seconds: int,
214
338
  ) -> None:
215
- """Restore a PostgreSQL logical backup from a local file."""
216
- _require_docker()
339
+ """Restore a PostgreSQL database from a logical backup file.
340
+
341
+ Restores the database from a SQL dump file (plain or gzip-compressed).
342
+ Uses psql internally to execute the dump SQL.
343
+
344
+ Warning:
345
+ This may overwrite existing data. Use with caution on production databases.
346
+
347
+ Args:
348
+ input_file: Path to the dump file (.sql or .sql.gz).
349
+ user: Database user (default from settings).
350
+ database: Database name (default from settings).
351
+ timeout_seconds: Maximum time to wait for restore completion.
352
+
353
+ Raises:
354
+ click.ClickException: If the restore fails or times out.
355
+
356
+ Example:
357
+ $ phlo postgres restore --file backup.sql
358
+ $ phlo postgres restore --file backup.sql.gz --db mydb --timeout 600
359
+
360
+ """
361
+ _require_container_backend()
217
362
  resolved_user, resolved_db = _postgres_identity(user=user, database=database)
218
363
  cmd = _postgres_exec_base(tty=False)
219
364
  cmd.extend(["psql", "-U", resolved_user, "-d", resolved_db, "-v", "ON_ERROR_STOP=1"])
@@ -257,8 +402,27 @@ def postgres_vacuum(
257
402
  analyze: bool,
258
403
  timeout_seconds: int,
259
404
  ) -> None:
260
- """Run vacuumdb inside the PostgreSQL service container."""
261
- _require_docker()
405
+ """Run vacuumdb for PostgreSQL maintenance inside the container.
406
+
407
+ Executes vacuumdb to reclaim storage and optionally update statistics.
408
+ This is useful for routine database maintenance after large operations.
409
+
410
+ Args:
411
+ user: Database user (default from settings).
412
+ database: Database name (default from settings).
413
+ analyze: Whether to run ANALYZE after vacuum (updates statistics).
414
+ timeout_seconds: Maximum time to wait for vacuum completion.
415
+
416
+ Raises:
417
+ click.ClickException: If vacuum fails or times out.
418
+
419
+ Example:
420
+ $ phlo postgres vacuum
421
+ $ phlo postgres vacuum --no-analyze
422
+ $ phlo postgres vacuum --db analytics --timeout 300
423
+
424
+ """
425
+ _require_container_backend()
262
426
  resolved_user, resolved_db = _postgres_identity(user=user, database=database)
263
427
  cmd = _postgres_exec_base(tty=False)
264
428
  cmd.extend(["vacuumdb", "-U", resolved_user])
@@ -0,0 +1,88 @@
1
+ """CLI plugin for PostgreSQL commands.
2
+
3
+ This module provides the CLI plugin implementation that registers PostgreSQL
4
+ commands with the phlo CLI system. It exposes the postgres command group and
5
+ its subcommands (query, dump, restore, vacuum) to the main phlo CLI.
6
+
7
+ Example:
8
+ >>> from phlo_postgres.cli_plugin import PostgresCliPlugin
9
+ >>> plugin = PostgresCliPlugin()
10
+ >>> commands = plugin.get_cli_commands()
11
+ >>> print(commands[0].name)
12
+ postgres
13
+
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import click
19
+
20
+ from phlo.plugins.base import CliCommandPlugin, PluginMetadata
21
+
22
+ from phlo_postgres.cli import postgres_group
23
+
24
+
25
+ class PostgresCliPlugin(CliCommandPlugin):
26
+ """CLI plugin that registers PostgreSQL commands with the phlo CLI.
27
+
28
+ This plugin provides the main entry point for PostgreSQL-related CLI
29
+ commands. It registers the postgres command group which includes
30
+ subcommands for querying, dumping, restoring, and maintaining PostgreSQL
31
+ databases.
32
+
33
+ Attributes:
34
+ None (uses class-level plugin registration).
35
+
36
+ Example:
37
+ >>> plugin = PostgresCliPlugin()
38
+ >>> print(plugin.metadata.name)
39
+ postgres
40
+ >>> commands = plugin.get_cli_commands()
41
+ >>> print([cmd.name for cmd in commands])
42
+ ['postgres']
43
+
44
+ """
45
+
46
+ @property
47
+ def metadata(self) -> PluginMetadata:
48
+ """Return plugin metadata for the PostgreSQL CLI plugin.
49
+
50
+ Returns:
51
+ PluginMetadata: Metadata describing the CLI plugin including name,
52
+ version, and description for plugin discovery.
53
+
54
+ Example:
55
+ >>> plugin = PostgresCliPlugin()
56
+ >>> meta = plugin.metadata
57
+ >>> print(f"{meta.name} v{meta.version}")
58
+ postgres v0.1.0
59
+ >>> print(meta.description)
60
+ CLI commands for PostgreSQL service access
61
+
62
+ """
63
+ return PluginMetadata(
64
+ name="postgres",
65
+ version="0.1.0",
66
+ description="CLI commands for PostgreSQL service access",
67
+ )
68
+
69
+ def get_cli_commands(self) -> list[click.Command]:
70
+ """Return CLI commands provided by this plugin.
71
+
72
+ Returns:
73
+ list[click.Command]: List of Click command objects to register
74
+ with the main phlo CLI. Currently provides the postgres
75
+ command group which includes query, dump, restore, and
76
+ vacuum subcommands.
77
+
78
+ Example:
79
+ >>> plugin = PostgresCliPlugin()
80
+ >>> commands = plugin.get_cli_commands()
81
+ >>> cmd = commands[0]
82
+ >>> print(cmd.name)
83
+ postgres
84
+ >>> print([c.name for c in cmd.commands.values()])
85
+ ['query', 'dump', 'restore', 'vacuum']
86
+
87
+ """
88
+ return [postgres_group]