1515
1616from __future__ import annotations
1717
18+ import hashlib
1819import json
1920from datetime import date
2021from pathlib import Path
@@ -56,13 +57,40 @@ def _load_dir(subdir: Path) -> list[dict[str, Any]]:
5657 return items
5758
5859
60+ # Primary keys are derived from the slug instead of an autoincrement counter.
61+ # The dump exposes `id`, so with a counter one inserted record renumbered every
62+ # row after it and the regenerated dump rewrote pages whose data never changed
63+ # (TechAPI #180: ~1M files, GitHub could not even render the diff).
64+ _ID_BITS = 48 # < 2**53, so the value survives JSON round-trips intact
65+
66+
67+ def _stable_id (table : str , slug : str , taken : set [int ]) -> int :
68+ """Deterministic id for ``table``/``slug``, avoiding ids already assigned."""
69+ attempt = 0
70+ while True :
71+ key = f"{ table } :{ slug } " if attempt == 0 else f"{ table } :{ slug } #{ attempt } "
72+ digest = hashlib .blake2b (key .encode ("utf-8" ), digest_size = 8 ).digest ()
73+ value = int .from_bytes (digest , "big" ) % (1 << _ID_BITS ) or 1
74+ if value not in taken :
75+ taken .add (value )
76+ return value
77+ attempt += 1 # collision: rehash rather than fall back to a counter
78+
79+
80+ def _with_id (obj : SQLModel , taken : set [int ]) -> SQLModel :
81+ obj .id = _stable_id (type (obj ).__tablename__ , obj .slug , taken ) # type: ignore[attr-defined]
82+ return obj
83+
84+
5985def _existing_slugs (session : Session , model : type [SQLModel ]) -> set [str ]:
6086 rows = session .exec (select (model )).all ()
6187 return {row .slug for row in rows } # type: ignore[attr-defined] # all data models have slug
6288
6389
6490def seed (session : Session , data_dir : Path = DATA_DIR ) -> dict [str , int ]:
6591 """Idempotently insert seed data. Returns counts of newly inserted rows."""
92+ # Ids assigned in this run; only used to break hash collisions.
93+ taken : set [int ] = set ()
6694 counts = {
6795 "brands" : 0 ,
6896 "socs" : 0 ,
@@ -87,7 +115,7 @@ def seed(session: Session, data_dir: Path = DATA_DIR) -> dict[str, int]:
87115 # `categories` lives in the JSON for browsing/validation only — the Brand
88116 # table model does not (yet) carry it, so drop before construction.
89117 record .pop ("categories" , None )
90- session .add (Brand (** record ))
118+ session .add (_with_id ( Brand (** record ), taken ))
91119 counts ["brands" ] += 1
92120 session .commit ()
93121
@@ -104,7 +132,7 @@ def seed(session: Session, data_dir: Path = DATA_DIR) -> dict[str, int]:
104132 raise ValueError (
105133 f"SoC '{ record ['slug' ]} ' references unknown brand '{ manufacturer } '"
106134 )
107- session .add (SoC (manufacturer_id = manufacturer_id , ** record ))
135+ session .add (_with_id ( SoC (manufacturer_id = manufacturer_id , ** record ), taken ))
108136 counts ["socs" ] += 1
109137 session .commit ()
110138
@@ -127,7 +155,8 @@ def seed(session: Session, data_dir: Path = DATA_DIR) -> dict[str, int]:
127155 raise ValueError (
128156 f"Smartphone '{ record ['slug' ]} ' references unknown SoC '{ soc_slug } '"
129157 )
130- session .add (Smartphone (brand_id = brand_id , soc_id = soc_id , ** record ))
158+ phone = Smartphone (brand_id = brand_id , soc_id = soc_id , ** record )
159+ session .add (_with_id (phone , taken ))
131160 counts ["smartphones" ] += 1
132161 session .commit ()
133162
@@ -150,7 +179,8 @@ def seed_mobile_devices(subdir: str, model: type[SQLModel], count_key: str) -> N
150179 f"{ subdir .rstrip ('s' ).title ()} '{ record ['slug' ]} ' "
151180 f"references unknown SoC '{ soc_slug } '"
152181 )
153- session .add (model (brand_id = brand_id , soc_id = soc_id , ** record ))
182+ device = model (brand_id = brand_id , soc_id = soc_id , ** record )
183+ session .add (_with_id (device , taken ))
154184 counts [count_key ] += 1
155185 session .commit ()
156186
@@ -169,7 +199,7 @@ def seed_mobile_devices(subdir: str, model: type[SQLModel], count_key: str) -> N
169199 raise ValueError (
170200 f"GPU '{ record ['slug' ]} ' references unknown brand '{ manufacturer } '"
171201 )
172- session .add (DiscreteGPU (manufacturer_id = manufacturer_id , ** record ))
202+ session .add (_with_id ( DiscreteGPU (manufacturer_id = manufacturer_id , ** record ), taken ))
173203 counts ["gpus" ] += 1
174204 session .commit ()
175205
@@ -184,7 +214,7 @@ def seed_mobile_devices(subdir: str, model: type[SQLModel], count_key: str) -> N
184214 raise ValueError (
185215 f"CPU '{ record ['slug' ]} ' references unknown brand '{ manufacturer } '"
186216 )
187- session .add (CPU (manufacturer_id = manufacturer_id , ** record ))
217+ session .add (_with_id ( CPU (manufacturer_id = manufacturer_id , ** record ), taken ))
188218 counts ["cpus" ] += 1
189219 session .commit ()
190220
@@ -213,7 +243,8 @@ def seed_mobile_devices(subdir: str, model: type[SQLModel], count_key: str) -> N
213243 raise ValueError (
214244 f"Laptop '{ record ['slug' ]} ' references unknown GPU '{ gpu_slug } '"
215245 )
216- session .add (Laptop (brand_id = brand_id , cpu_id = cpu_id , gpu_id = gpu_id , ** record ))
246+ laptop = Laptop (brand_id = brand_id , cpu_id = cpu_id , gpu_id = gpu_id , ** record )
247+ session .add (_with_id (laptop , taken ))
217248 counts ["laptops" ] += 1
218249 session .commit ()
219250
@@ -228,7 +259,7 @@ def seed_mobile_devices(subdir: str, model: type[SQLModel], count_key: str) -> N
228259 raise ValueError (
229260 f"Monitor '{ record ['slug' ]} ' references unknown brand '{ brand_slug } '"
230261 )
231- session .add (Monitor (brand_id = brand_id , ** record ))
262+ session .add (_with_id ( Monitor (brand_id = brand_id , ** record ), taken ))
232263 counts ["monitors" ] += 1
233264 session .commit ()
234265
@@ -237,7 +268,7 @@ def seed_mobile_devices(subdir: str, model: type[SQLModel], count_key: str) -> N
237268 for record in _load_dir (data_dir / "game" ):
238269 if record ["slug" ] in game_slugs :
239270 continue
240- session .add (Game (** record ))
271+ session .add (_with_id ( Game (** record ), taken ))
241272 counts ["games" ] += 1
242273 session .commit ()
243274
@@ -246,7 +277,7 @@ def seed_mobile_devices(subdir: str, model: type[SQLModel], count_key: str) -> N
246277 for record in _load_dir (data_dir / "software" ):
247278 if record ["slug" ] in software_slugs :
248279 continue
249- session .add (Software (** record ))
280+ session .add (_with_id ( Software (** record ), taken ))
250281 counts ["software" ] += 1
251282 session .commit ()
252283
@@ -255,7 +286,7 @@ def seed_mobile_devices(subdir: str, model: type[SQLModel], count_key: str) -> N
255286 for record in _load_dir (data_dir / "website" ):
256287 if record ["slug" ] in website_slugs :
257288 continue
258- session .add (Website (** record ))
289+ session .add (_with_id ( Website (** record ), taken ))
259290 counts ["websites" ] += 1
260291 session .commit ()
261292
0 commit comments