openframe-adapters-db-postgres 1.1.0__tar.gz → 1.2.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 (15) hide show
  1. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/PKG-INFO +1 -1
  2. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/openframe/adapters/db/postgres/plugin.py +40 -3
  3. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/pyproject.toml +1 -1
  4. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/tests/test_plugin.py +67 -2
  5. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/.gitignore +0 -0
  6. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/README.md +0 -0
  7. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/openframe/adapters/db/postgres/__init__.py +0 -0
  8. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/openframe/adapters/db/postgres/config.py +0 -0
  9. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/openframe/adapters/db/postgres/connection.py +0 -0
  10. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/openframe/adapters/db/postgres/repository.py +0 -0
  11. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/tests/conftest.py +0 -0
  12. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/tests/test_config.py +0 -0
  13. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/tests/test_connection.py +0 -0
  14. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/tests/test_health.py +0 -0
  15. {openframe_adapters_db_postgres-1.1.0 → openframe_adapters_db_postgres-1.2.0}/tests/test_repository.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openframe-adapters-db-postgres
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: OpenFrame Microservice Suite — PostgreSQL database adapter.
5
5
  Project-URL: Homepage, https://github.com/Furious-Meteors/openframe-adapters
6
6
  Project-URL: Documentation, https://furious-meteors.github.io/openframe-adapters/
@@ -22,6 +22,9 @@ Usage via deps.py (unchanged, no plugin needed)::
22
22
  repo = PostgresRepository(PostgresSettings())
23
23
  traced = TracingProxy(repo, prefix="repository.item")
24
24
  """
25
+ # Capability: "persistence"
26
+ # See capability taxonomy:
27
+ # https://furious-meteors.github.io/openframe-core/developer-guide/composition-root/
25
28
  from __future__ import annotations
26
29
 
27
30
  import logging
@@ -44,6 +47,8 @@ class PostgresPlugin:
44
47
 
45
48
  Capability: "persistence"
46
49
 
50
+ Stability: beta
51
+
47
52
  Lifecycle:
48
53
  initialize() — creates the asyncpg connection pool and verifies
49
54
  connectivity via ping(). Raises AdapterConnectionError
@@ -53,10 +58,20 @@ class PostgresPlugin:
53
58
 
54
59
  The plugin exposes get_repository() after initialization for use
55
60
  in the composition root or ApplicationBootstrap.
61
+
62
+ By default constructs a plain PostgresRepository. To use a domain-specific
63
+ subclass, pass it via repository_class::
64
+
65
+ registry.register(PostgresPlugin(
66
+ PostgresSettings(),
67
+ table="items",
68
+ id_column="id",
69
+ repository_class=ItemPostgresRepository,
70
+ ))
56
71
  """
57
72
 
58
73
  name: str = "openframe-postgres"
59
- version: str = "1.1.0"
74
+ version: str = "1.2.0"
60
75
  capability: str = "persistence"
61
76
 
62
77
  def __init__(
@@ -64,10 +79,31 @@ class PostgresPlugin:
64
79
  settings: PostgresSettings,
65
80
  table: str = "",
66
81
  id_column: str = "id",
82
+ repository_class: type[PostgresRepository] = PostgresRepository,
67
83
  ) -> None:
84
+ """
85
+ Args:
86
+ settings: PostgresSettings instance.
87
+ table: Table name. If omitted the plugin acts as a
88
+ connection manager only (no get_repository()).
89
+ id_column: Primary key column name. Defaults to "id".
90
+ repository_class: The PostgresRepository subclass to construct.
91
+ Defaults to the base PostgresRepository. Pass a
92
+ domain-specific subclass here to get proper
93
+ entity mapping through get_repository().
94
+
95
+ Raises:
96
+ TypeError: repository_class is not a subclass of PostgresRepository.
97
+ """
98
+ if not (isinstance(repository_class, type) and issubclass(repository_class, PostgresRepository)):
99
+ raise TypeError(
100
+ f"repository_class must be a subclass of PostgresRepository, "
101
+ f"got {repository_class!r}"
102
+ )
68
103
  self._settings = settings
69
104
  self._table = table
70
105
  self._id_column = id_column
106
+ self._repository_class = repository_class
71
107
  self._repo: PostgresRepository | None = None
72
108
  self._status = PluginStatus.REGISTERED
73
109
 
@@ -94,7 +130,7 @@ class PostgresPlugin:
94
130
  try:
95
131
  pool = await get_postgres_pool(self._settings)
96
132
  if self._table:
97
- self._repo = PostgresRepository(
133
+ self._repo = self._repository_class(
98
134
  self._settings,
99
135
  table=self._table,
100
136
  id_column=self._id_column,
@@ -117,8 +153,9 @@ class PostgresPlugin:
117
153
  ) from exc
118
154
  self._status = PluginStatus.READY
119
155
  _logger.info(
120
- "PostgresPlugin initialized — %s",
156
+ "PostgresPlugin initialized — %s (repository_class=%s)",
121
157
  self._settings.database_url.split("@")[-1],
158
+ self._repository_class.__name__,
122
159
  )
123
160
  except Exception:
124
161
  self._status = PluginStatus.FAILED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openframe-adapters-db-postgres"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  description = "OpenFrame Microservice Suite — PostgreSQL database adapter."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -7,7 +7,7 @@ from __future__ import annotations
7
7
 
8
8
  import pytest
9
9
 
10
- from openframe.adapters.db.postgres import PostgresPlugin, PostgresSettings
10
+ from openframe.adapters.db.postgres import PostgresPlugin, PostgresRepository, PostgresSettings
11
11
  from openframe.core.plugins import OpenFramePlugin, PluginContext, PluginStatus
12
12
 
13
13
 
@@ -46,7 +46,7 @@ def test_postgres_plugin_name(plugin):
46
46
 
47
47
 
48
48
  def test_postgres_plugin_version(plugin):
49
- assert plugin.version == "1.1.0"
49
+ assert plugin.version == "1.2.0"
50
50
 
51
51
 
52
52
  def test_postgres_plugin_capability(plugin):
@@ -177,3 +177,68 @@ async def test_health_returns_failed_when_ping_fails(
177
177
  assert result is not None
178
178
  assert result.status == PluginStatus.FAILED
179
179
  conn_module._pool_cache.clear()
180
+
181
+
182
+ # ── repository_class parameter ─────────────────────────────────────────────
183
+
184
+ def test_plugin_defaults_to_base_repository_class(settings):
185
+ """Backwards compatibility — no repository_class passed."""
186
+ plugin = PostgresPlugin(settings, table="items")
187
+ assert plugin._repository_class is PostgresRepository
188
+
189
+
190
+ def test_plugin_accepts_custom_repository_class(settings):
191
+ class CustomRepo(PostgresRepository):
192
+ pass
193
+
194
+ plugin = PostgresPlugin(settings, table="items", repository_class=CustomRepo)
195
+ assert plugin._repository_class is CustomRepo
196
+
197
+
198
+ def test_plugin_rejects_non_repository_class(settings):
199
+ """repository_class must be a subclass of PostgresRepository — TypeError if not."""
200
+ with pytest.raises(TypeError, match="subclass of PostgresRepository"):
201
+ PostgresPlugin(settings, table="items", repository_class=object) # type: ignore[arg-type]
202
+
203
+
204
+ async def test_initialize_constructs_custom_repository_class(
205
+ settings, plugin_context, mock_pool, mock_settings
206
+ ):
207
+ """
208
+ REGRESSION: plugin always constructed the base class, silently discarding
209
+ domain subclass overrides of entity mapping methods.
210
+ """
211
+ import openframe.adapters.db.postgres.connection as conn_module
212
+
213
+ class CustomRepo(PostgresRepository):
214
+ marker = True
215
+
216
+ conn_module._pool_cache[mock_settings.database_url] = mock_pool
217
+ mock_pool.fetchval.return_value = 1
218
+
219
+ plugin = PostgresPlugin(mock_settings, table="items", repository_class=CustomRepo)
220
+ await plugin.initialize(plugin_context)
221
+
222
+ repo = plugin.get_repository()
223
+ assert isinstance(repo, CustomRepo)
224
+ assert hasattr(repo, "marker")
225
+ conn_module._pool_cache.clear()
226
+
227
+
228
+ async def test_get_repository_returns_subclass_not_base_class(
229
+ settings, plugin_context, mock_pool, mock_settings
230
+ ):
231
+ """type(repo) must be the subclass, not just isinstance-compatible."""
232
+ import openframe.adapters.db.postgres.connection as conn_module
233
+
234
+ class CustomRepo(PostgresRepository):
235
+ pass
236
+
237
+ conn_module._pool_cache[mock_settings.database_url] = mock_pool
238
+ mock_pool.fetchval.return_value = 1
239
+
240
+ plugin = PostgresPlugin(mock_settings, table="items", repository_class=CustomRepo)
241
+ await plugin.initialize(plugin_context)
242
+
243
+ assert type(plugin.get_repository()) is CustomRepo
244
+ conn_module._pool_cache.clear()