Repository navigation
Finishes what 0.5.0 deferred, and fixes what turned up on the way. Every
backend the README names now ships, is tested and reads all three DDI
flavors; ddigraph validate checks graphs as well as XML; and the RDF
vocabulary covers statistical classifications. Three silent data-loss bugs
are fixed: namespaced DDI-Codebook input, and two in the JSON/CSV export.
Of the 0.5.0 "Deferred to 0.6.0" list, all but content negotiation on the
vocabulary namespace landed here; that one needs hosting the project does
not have.
This entry grows as the release lands; it is not yet dated or published.
Upgrading from 0.5.0
Four changes alter output that 0.5.0 users may already depend on. Each one
fixes something that was wrong, but check them before upgrading.
- JSON and CSV exports: node records carry the type as
node_label,
notlabel, plus a newnode_id. In 0.5.0,labelheld the type for
some nodes and the display name for most. It is now always the display
name, the node's own property. - Composite-identity relationships in JSON and CSV name their
endpoints by every identity part, joined with|. Single-key endpoints
are unchanged. - RDF types:
Datasetnodes aredcat:Dataset, not
dcterms:Dataset, andCDIStatisticalClassificationis
skos:ConceptScheme, notxkos:ClassificationLevel. Queries that
matched the old types need updating. Theddigraph:project types are
unchanged. ddigraph validateexits 2, not 1, when it cannot check a file at
all, such as a flavor with no bundled schema.
Added
-
ddigraph validatechecks graphs against the SHACL shapes. XSD
answers "is this valid DDI?"; SHACL answers "is this the graph ddigraph
promises?", which is the question whoever receives an export is asking.
Until now it tookddigraph shapes, apyshaclinstall and a script.ddigraph validate survey.ttl --flavor lifecycle # an RDF export ddigraph validate survey.xml --shapes # XSD, then SHACL
RDF input is recognised by extension and checked against the shapes
alone. DDI XML is projected the wayexportwould and checked against
the shapes for its detected flavor. Results are read from pyshacl's
report graph rather than its text, sorted so a report is stable, and
--jsoncarries each one's focus node, path and constraint. An RDF file
that does not parse is reported as not conforming (exit 1), not as a
traceback. Also available asddigraph.validation.validate_shapes(),
which raisesGraphParseErrorfor such a file. Needs the[shacl]
extra. -
NetworkX, pandas and Gremlin adapters ship in the package
(ddigraph.backends). Before, they weredemo/scripts outside both
the wheel and the sdist, and the Gremlin and pandas ones only read
DDI-L. Each is one call, takes DDI XML of any flavor or an RDF export,
and is tested:from ddigraph.backends.networkx import to_networkx from ddigraph.backends.pandas import to_dataframes from ddigraph.backends.gremlin import write_gremlin G = to_networkx("survey.xml") # nx.MultiDiGraph frames = to_dataframes("survey.xml") # frames.nodes, frames.relationships result = write_gremlin(g, "survey.xml") # any TinkerPop server
- NetworkX: node ids are
"<label>:<key>", because a key is only
unique within its label; the type isnode_type, so it cannot
collide with a record's ownlabel. Edges are keyed by type, so a
reload adds nothing.flatten_lists=Truemakes the graph writable
as GraphML or GEXF. - pandas: the same columns as the CSV export, and
frames.of(label)
for one type with only the columns it uses. - Gremlin: idempotent upserts in batched requests. Vertices are
found by label and anode_keyproperty; edges are written after
every vertex, so a codebook edge that points at a later batch is
not lost.labelandidproperties becomeddi_labeland
ddi_id, because Cosmos DB and others reserve them; lists are
joined with|. The result counts any edge skipped for a missing
endpoint. Verified against Apache TinkerPop Gremlin Server 3.8.2 by
tests/test_backends_gremlin_live.py, which runs when
GREMLIN_URLis set.
New backend pages for pandas, rewritten NetworkX and Gremlin pages, and
quickstart tabs for all three, in both languages. The Gremlin page's
"full example" calledfragment.element_type,.referencesand
.to_dict(), none of which exist, and its path query used a
relationship type DDI-L does not emit (ASKS_QUESTION, not
REFERENCES_QUESTION). - NetworkX: node ids are
-
Classification structure is aligned to XKOS, for DDI-L and DDI-CDI
alike. Each alignment was checked against the XKOS specification
rather than inferred from names; a wrong alignment is worse than none,
because consumers act on it.- Classes:
StatisticalClassificationand its CDI twin are
skos:ConceptScheme("each major version of a classification is
represented in XKOS by askos:ConceptScheme");ClassificationItem
andCDIClassificationItemareskos:Concept;ClassificationLevel
isxkos:ClassificationLevel; the three correspondence-table types
arexkos:CorrespondenceandCDIConceptMapis
xkos:ConceptAssociation;CDICategorySetisskos:ConceptScheme;
DataSetandCDIDataSetaredcat:Dataset. - Predicates:
HAS_CLASSIFICATION_ITEMbecomesskos:inSchemefrom
the item's side, likeHAS_CATEGORY;HAS_CONCEPT_MAPbecomes
xkos:madeOf;IS_SUCCESSOR_OFbecomesxkos:follows, and
IS_PREDECESSOR_OFthe same predicate turned around, because XKOS
reads "NACE rev. 2 followed NACE rev. 1.1".
Left unaligned on purpose: XKOS defines no class for a classification
series or family, andMAPS/MAPS_TOalso join record relations to
logical records, wherexkos:compareswould be wrong. 18 of the 32
curated DDI-CDI types were unaligned; 11 still are. - Classes:
Changed
ddigraph validateexits 2 when it cannot check a file -- no
bundled schema for the flavor, or the[shacl]extra missing -- where
it used to exit 1, the same as a file that fails. Scripts can now tell
bad data from a broken setup.0and1are unchanged.pandasandgremlinpythonare in[dev], so the new adapters'
tests and runnable doc examples execute in CI rather than skip.
Fixed
-
JSON and CSV exports wrote the node type where the display label
belonged, and could not be joined. Two faults in the same record:- The node type went under
label, and the node's ownlabel
property -- its human-readable name, which most nodes have -- then
overwrote it. The type column held names like "Test Instrument", and
the JSON summary'snodes_by_labelcounted those names. - A relationship named its endpoints by their first identity value
alone. A composite identity's first part is shared: all fourteen
DDIGenericIdentifiablenodes in the sample codebook are keyed on
(dataset_id, element_tag, identifiable_id), so every edge leaving
any of them named the same start node.
Node records now carry
node_label(the type) andnode_id,
written after the properties so nothing can overwrite them, and
start_id/end_iduse the same key.node_idis the identity value
itself for single-key nodes, so those edges read exactly as before; a
composite key joins its parts with|. Thelabelkey is now always
the property. The key function is public as
ddigraph.graph.node_key(). RDF output was never affected: its writer
already used every identity part. - The node type went under
-
Datasetnodes were typed with a class that does not exist.
dcterms:Datasetis not in DCMI Metadata Terms --Datasetlives in
the separate DCMI Type vocabulary -- so every exported dataset carried
an undefined type. It is nowdcat:Dataset, the W3C class data
catalogs read, anddcat:is a bound prefix. -
CDIStatisticalClassificationwas typedxkos:ClassificationLevel.
A level is a layer within a classification; the classification itself
is askos:ConceptScheme. Both shipped in 0.5.0, so RDF exported with
it carries the old types; re-export to get the corrected ones. -
A DDI-Codebook file in its official namespace silently lost data.
Real codebooks declareddi:codebook:2_5(or2_6); the test fixtures
do not, and the demo corpus is all DDI-L, so no namespaced codebook had
ever been parsed in CI. The top-level dispatch ignored namespaces, but a
dozen nested lookups -- questions, universes, categories, concepts,
series, groups, collection events, and every label -- matched bare tag
names only. On the sample codebook, 9 of 62 nodes and 17 of 73
relationships disappeared with no error. Lookups now match in any
namespace, and a test requires a namespaced copy of each codebook
fixture to project an identical graph, properties included.The same change fills two labels the parser had been dropping on mixed
documents: a<l:labl>inside a DDI-LlogicalRecordor
physicalStructureembedded in a codebook was invisible to the bare
labllookup.
Removed
demo/load_networkx.py,demo/load_pandas.pyand
demo/load_gremlin.py, superseded byddigraph.backends, as
demo/load_rdf.pywas byddigraph exportin 0.5.0.
Testing
- Line endings are LF on every checkout.
.gitattributesnow sets
eol=lf, so a Windows clone withcore.autocrlf=trueno longer fails
the schema checksum andruff formatgates on bytes nobody changed. - CI runs on Windows (Python 3.12, every blocking gate), alongside the
three Linux legs. The two failures above were invisible to a
Linux-only matrix. - The served RDF vocabulary is checked in CI.
vocabulary.ttlis
generated from the alignment tables and published at the namespace IRI,
but nothing caught it drifting from them. - Python 3.15 release candidates run in CI, non-blocking. The
requires-pythoncap stays at<3.15until 3.15.0 final passes. - Documented bash examples run on Windows. A bare
"bash"in
subprocessresolves to the WSL launcher inSystem32beforePATHis
searched, and WSL cannot see the virtualenv. The test now finds bash on
PATH(Git Bash) and skips the bash examples when only the WSL
launcher is available.