Add GEDCOM import/export
A pragmatic GEDCOM parser + mapper: import reads INDI/FAM/SOUR and creates people, names, life events, partnership + qualified parent-child relationships, marriage events, places (deduped), sources, and citations from SOUR refs — returning a mapping report (counts + unmapped tags). Export serializes the tree back to GEDCOM (families derived from the edge model). Import is additive (no merge) and runs inline for now. Round-trip test passes; 29 tests total. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Paul <justin@jpaul.me>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""GEDCOM import + export round-trip."""
|
||||
|
||||
from tests.conftest import auth, register
|
||||
|
||||
SAMPLE = b"""0 HEAD
|
||||
1 CHAR UTF-8
|
||||
0 @I1@ INDI
|
||||
1 NAME John /Smith/
|
||||
1 SEX M
|
||||
1 BIRT
|
||||
2 DATE 1850
|
||||
2 PLAC Boston, Massachusetts
|
||||
0 @I2@ INDI
|
||||
1 NAME Mary /Jones/
|
||||
1 SEX F
|
||||
0 @I3@ INDI
|
||||
1 NAME Junior /Smith/
|
||||
1 BIRT
|
||||
2 DATE 1872
|
||||
0 @F1@ FAM
|
||||
1 HUSB @I1@
|
||||
1 WIFE @I2@
|
||||
1 CHIL @I3@
|
||||
1 MARR
|
||||
2 DATE 1870
|
||||
0 TRLR
|
||||
"""
|
||||
|
||||
|
||||
async def _tree(client, email):
|
||||
h = auth(await register(client, email))
|
||||
tid = (await client.post("/api/v1/trees", json={"name": "Imported"}, headers=h)).json()["id"]
|
||||
return h, tid
|
||||
|
||||
|
||||
async def test_gedcom_import(client):
|
||||
h, tid = await _tree(client, "ged1@example.com")
|
||||
resp = await client.post(
|
||||
f"/api/v1/trees/{tid}/gedcom/import",
|
||||
files={"file": ("sample.ged", SAMPLE, "text/plain")},
|
||||
headers=h,
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
counts = resp.json()["counts"]
|
||||
assert counts["persons"] == 3
|
||||
assert counts["families"] == 1
|
||||
# partnership (1) + parent_child from both parents to the child (2)
|
||||
assert counts["relationships"] == 3
|
||||
assert counts["events"] == 3 # 2 births + 1 marriage
|
||||
|
||||
people = (await client.get(f"/api/v1/trees/{tid}/persons", headers=h)).json()
|
||||
assert len(people) == 3
|
||||
rels = (await client.get(f"/api/v1/trees/{tid}/relationships", headers=h)).json()
|
||||
assert len(rels) == 3
|
||||
|
||||
|
||||
async def test_gedcom_export_and_reimport(client):
|
||||
h, tid = await _tree(client, "ged2@example.com")
|
||||
await client.post(
|
||||
f"/api/v1/trees/{tid}/gedcom/import",
|
||||
files={"file": ("sample.ged", SAMPLE, "text/plain")},
|
||||
headers=h,
|
||||
)
|
||||
exported = await client.get(f"/api/v1/trees/{tid}/gedcom/export", headers=h)
|
||||
assert exported.status_code == 200
|
||||
text = exported.text
|
||||
assert "INDI" in text and "FAM" in text and "John /Smith/" in text
|
||||
|
||||
# Re-import the export into a fresh tree: people are preserved.
|
||||
tid2 = (await client.post("/api/v1/trees", json={"name": "Round"}, headers=h)).json()["id"]
|
||||
resp = await client.post(
|
||||
f"/api/v1/trees/{tid2}/gedcom/import",
|
||||
files={"file": ("rt.ged", text.encode(), "text/plain")},
|
||||
headers=h,
|
||||
)
|
||||
assert resp.json()["counts"]["persons"] == 3
|
||||
assert resp.json()["counts"]["relationships"] == 3
|
||||
Reference in New Issue
Block a user