pgdevkit 0.6.0__py3-none-any.whl → 0.6.2__py3-none-any.whl

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.
@@ -63,6 +63,17 @@ both together.
63
63
  One object per file: `tables/user.sql`, `views/all_edits.sql`,
64
64
  `types/measurement_unit.sql`.
65
65
 
66
+ **`permissions` files always apply last, genuinely.** A blanket `GRANT ...
67
+ ON ALL TABLES IN SCHEMA x TO role;` only covers what exists at the moment it
68
+ runs — so if a table with a cross-file dependency that isn't resolved on the
69
+ first pass (e.g. an FK to a table in another layer directory whose filename
70
+ happens to sort later) got created *after* a `permissions` file, that grant
71
+ would silently never cover it. `pgdb testdb` holds every `permissions` file
72
+ back unconditionally and applies it only once the whole rest of the tree
73
+ (delayed-retry resolution included) has actually finished — so write
74
+ `permissions` files as if every table/view they reference is guaranteed to
75
+ already exist, because it is.
76
+
66
77
  ---
67
78
 
68
79
  ## File-naming conventions
@@ -11,6 +11,13 @@ PORT = int(os.environ.get("PGDEVKIT_TESTDB_PORT", "54322"))
11
11
  USER = os.environ.get("PGDEVKIT_TESTDB_USER", "postgres")
12
12
  PASSWORD = os.environ.get("PGDEVKIT_TESTDB_PASSWORD", "testpwd")
13
13
  PG_SPEED_FLAGS = ["-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
14
+ # Production connects with session timezone=UTC; the container image's own default
15
+ # (baked into its base OS, not something pgdevkit ever set) can differ, silently
16
+ # making any ::date cast of a timestamptz column disagree between test and prod for
17
+ # rows near local midnight. Forcing UTC here -- both as the server's own timezone GUC
18
+ # and as PGTZ/TZ for any client library that consults the environment instead -- keeps
19
+ # test parity with prod instead of depending on the host/image's locale.
20
+ PG_STARTUP_FLAGS = [*PG_SPEED_FLAGS, "-c", "timezone=UTC"]
14
21
 
15
22
 
16
23
  def conninfo(dbname: str, *, connect_timeout: int | None = None) -> str:
@@ -36,8 +36,13 @@ def _create_container(client: docker.DockerClient) -> None:
36
36
  environment={
37
37
  "POSTGRES_USER": constants.USER,
38
38
  "POSTGRES_PASSWORD": constants.PASSWORD,
39
+ # Belt-and-suspenders alongside the server's own -c timezone=UTC below:
40
+ # some client libraries/tools inside the container consult TZ/PGTZ
41
+ # directly instead of asking Postgres for its configured timezone.
42
+ "TZ": "UTC",
43
+ "PGTZ": "UTC",
39
44
  },
40
- command=["postgres", *constants.PG_SPEED_FLAGS],
45
+ command=["postgres", *constants.PG_STARTUP_FLAGS],
41
46
  )
42
47
  except docker.errors.APIError as e:
43
48
  if getattr(e, "status_code", None) == 409 or "already in use" in str(e):
pgdevkit/testdb/schema.py CHANGED
@@ -132,6 +132,19 @@ def _iter_sql_files(
132
132
  ):
133
133
  """Yield (Path, sql_content) pairs in dependency-safe execution order.
134
134
 
135
+ Every `permissions`-type file (see `_TYPE_ORDER`) is held back
136
+ unconditionally and yielded only after every other file -- including
137
+ anything resolved via the delayed-retry loop below -- has actually been
138
+ delivered. A blanket "grant ... on all tables in schema X" has no real
139
+ per-object dependency of its own to track, but it still must apply
140
+ *after* every table/view it's meant to cover; a table whose own creation
141
+ got deferred to the retry loop (e.g. an FK to a same-type file that
142
+ happens to sort later) would otherwise silently miss that grant, since
143
+ a schema-wide GRANT is a one-time snapshot of whatever exists when it
144
+ runs. Nothing ever legitimately depends on a permissions file (it
145
+ creates no table/view/schema of its own), so holding every one back is
146
+ always safe.
147
+
135
148
  A file tagged for another environment (see pgdevkit.envtag) is dropped
136
149
  during the initial file walk, before dependency ordering even sees it.
137
150
 
@@ -154,6 +167,7 @@ def _iter_sql_files(
154
167
  delivered: set[str] = set()
155
168
  delayed: list[tuple[str | None, Path, str]] = []
156
169
  all_declared: set[str] = set()
170
+ permissions_files: list[tuple[Path, str]] = []
157
171
 
158
172
  for file in sorted(files, key=lambda p: (_get_type_order(p), p.name)):
159
173
  content = file.read_text(encoding="utf-8")
@@ -163,6 +177,9 @@ def _iter_sql_files(
163
177
  sql_schemas(content, dialect), only=schemas, exclude=exclude_schemas
164
178
  ):
165
179
  continue
180
+ if _get_type_order(file) == _TYPE_ORDER["permissions"]:
181
+ permissions_files.append((file, content))
182
+ continue
166
183
  deps = _get_sql_deps(content, dialect)
167
184
  if file.parent.name in _SCHEMA_QUALIFIED_TYPES:
168
185
  schema = _strip_layer_prefix(file.parent.parent.name)
@@ -196,6 +213,8 @@ def _iter_sql_files(
196
213
  if not progressed:
197
214
  raise ValueError(f"Circular or missing SQL dependencies: {[f[1] for f in delayed]}")
198
215
 
216
+ yield from permissions_files
217
+
199
218
 
200
219
  async def _insert_test_data(
201
220
  json_file: Path,
@@ -276,6 +295,10 @@ async def apply_schema(
276
295
  """Apply every .sql file under database_dir (in dependency-safe order)
277
296
  and seed any matching .test_data.json files. Safe to call repeatedly.
278
297
 
298
+ Every `permissions`-type file (see `_TYPE_ORDER`) is guaranteed to apply
299
+ after every table/view it's meant to cover, including anything resolved
300
+ via delayed-retry -- see `_iter_sql_files()`'s docstring.
301
+
279
302
  A file tagged for another environment (e.g. `grants.prod.sql` when
280
303
  `env="local_test"`) is skipped — see pgdevkit.envtag.
281
304
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pgdevkit
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: A helper for developing with Postgres
5
5
  Requires-Python: >=3.14
6
6
  Requires-Dist: docker>=7.1.0
@@ -30,20 +30,20 @@ pgdevkit/testdb/__init__.py,sha256=LpdC2XMBxqn9JrGmXhIho3sK8O1aNo_aD7leKyBr-Lo,2
30
30
  pgdevkit/testdb/_docker.py,sha256=kj_0xrGO_mxHlK83ilsB-yE0ELX1kpH22ruYrS-N3iU,1467
31
31
  pgdevkit/testdb/api.py,sha256=K-y4O6nC3z_gr9LtVHe-v-T-1UjhmFMw1j-dLa0FcnY,7680
32
32
  pgdevkit/testdb/config.py,sha256=vjcDr3jH3-5VakNAEMqQu_ZHPXiYIvM1_BSETSJW5QU,2164
33
- pgdevkit/testdb/constants.py,sha256=xi8uIoEgN9LrRkr41j3mmWB9PqqU0r8aCUb2JJXBAs0,1165
34
- pgdevkit/testdb/container.py,sha256=kELTb6qaECAO9a5Bwvx1btv6i5QKRFAa9PX6auWymNo,2807
33
+ pgdevkit/testdb/constants.py,sha256=oykc7xoX2R0ykhjXpqIdwkZoeHcScme8S2UPYvi_d_w,1718
34
+ pgdevkit/testdb/container.py,sha256=PD2xUksIlAWXXus2VtAil_8UnwlG5DUcyUvbZx0eZTg,3123
35
35
  pgdevkit/testdb/naming.py,sha256=Kyk5qEfxUkbggUG5bD7U7CJ_kAET332VPQNB-O5kvTY,1318
36
36
  pgdevkit/testdb/query.py,sha256=iYpy_EoYIq5USfwr6pNktmDjcGi_yQx0vHvjHZNGRr0,3854
37
- pgdevkit/testdb/schema.py,sha256=tLd5ovQSXdFoOOl9KWBI35wZHqhD_XIgWfwBVDIZJj0,13838
37
+ pgdevkit/testdb/schema.py,sha256=XYBK3xyay98a5gzFW6SAQ336Jge-NhOQhildSQDOaf4,15101
38
38
  pgdevkit/testdb/mssql/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
39
39
  pgdevkit/testdb/mssql/api.py,sha256=sFVSSQbN3Jk62YRQkT6atLSsl1-8hJVjCko5goOQkio,8374
40
40
  pgdevkit/testdb/mssql/constants.py,sha256=7n_NcYuhabbtb18CqG_H_rRbI7Wb_qu_l4nKk3281vI,2847
41
41
  pgdevkit/testdb/mssql/container.py,sha256=_rG0AMDXrNwok_E60Xai0DZdRiZ6Ba1j3GaLUnSAueg,3364
42
- pgdevkit/docs/database-layout.md,sha256=eqsEWuA0Vuqj5jRCXx24J45D1EjnDe3KNUQLxpdsW9M,7454
42
+ pgdevkit/docs/database-layout.md,sha256=j9N8GuT63D2oVf9rIOEv8GTWs4yTtjZTGH-zM5Wrg5o,8163
43
43
  pgdevkit/skills/pgdevkit/SKILL.md,sha256=ADqEaQtMJPUUAiieqokzUUmIf27bWWJNB1ZvsfgiKAE,12205
44
44
  pgdevkit/skills/pgdevkit/references/dynamic-sql.md,sha256=aTR1sx81moiTwaMaiOecaqk-9rT8y5Zxz7W1BoC3vK0,2774
45
45
  pgdevkit/skills/pgdevkit/references/temporal-tables.md,sha256=LwZnItvQWI7bB0oRHxIfUc0ynnq7N0V5Mnv-QHvFN3Q,1364
46
- pgdevkit-0.6.0.dist-info/METADATA,sha256=_aDm11tWJ9KphnjydC8Wq2nAKNkHd_xprRYiztnlPmc,18982
47
- pgdevkit-0.6.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
48
- pgdevkit-0.6.0.dist-info/entry_points.txt,sha256=FK2P55lf0WaGlOZJIINOGgCt8WB7ep0OnPsgnrp80bg,42
49
- pgdevkit-0.6.0.dist-info/RECORD,,
46
+ pgdevkit-0.6.2.dist-info/METADATA,sha256=fwy52aFbJK5YxpalDkQhB5-ZS0q9P9ooDPSoRkMS5lY,18982
47
+ pgdevkit-0.6.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
48
+ pgdevkit-0.6.2.dist-info/entry_points.txt,sha256=FK2P55lf0WaGlOZJIINOGgCt8WB7ep0OnPsgnrp80bg,42
49
+ pgdevkit-0.6.2.dist-info/RECORD,,