Metadata-Version: 2.5
Name: iirds
Version: 0.3.2
Summary: Read and write iiRDS packages. The seed of a community SDK, held in stewardship for the iiRDS ecosystem.
Project-URL: Homepage, https://github.com/dev365code/iirds
Project-URL: Validator, https://github.com/dev365code/iirds-validate
Author-email: Wooyong Lee <zero8004paz@gmail.com>
License: Apache-2.0
License-File: LICENSE
Keywords: iirds,intelligent-information,rdf,technical-documentation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Requires-Dist: rdflib>=6
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# iirds

Read and write [iiRDS](https://iirds.org) packages in Python.

```sh
pip install iirds
```

```python
import iirds

with iirds.open("machine-docs.iirds") as pkg:
    print(pkg.version, pkg.variant)      # "1.3" "unrestricted"
    graph = pkg.graph                    # rdflib.Graph of the package metadata
    data = pkg.read("content/topic1.xhtml")

iirds.pack("my-package-directory/")     # → my-package-directory.iirds,
                                         #   mimetype first and stored,
                                         #   byte-identical on every run
```

Deliberately small: open a container, get the metadata as an RDF graph, read
files, write a conformant container back. **It does not validate** — that is
[iirds-validate](https://github.com/dev365code/iirds-validate)'s job (185
rules, offline, CI-friendly), and keeping the two apart keeps this library at
one dependency (rdflib).

Two things the `pack()` half gets right that generic ZIP tooling gets wrong:
the `mimetype` entry is first and stored uncompressed, and packing the same
directory twice produces byte-identical output (honouring
`SOURCE_DATE_EPOCH`), so "this archive came from that directory" is checkable
with a hash instead of taken on trust.

## Stewardship

The `iirds` name on PyPI belongs to the standard's community more than to any
one project. **Should the iiRDS Consortium want this name for an official
SDK, it will be transferred on request** — until then it does real work
rather than squatting. `iirds-sdk` is an alias of this package and travels
under the same pledge.

This is an unofficial project, not affiliated with or endorsed by the iiRDS
Consortium or tekom Deutschland e.V. "iiRDS" is used descriptively, to name
the standard these functions read and write.

## Queries

`Package.instances_of(cls)`, `is_instance(node, cls)` and `label_of(node)`
(also module-level, taking any rdflib graph) answer "what is in this
package" with **section-7 semantics**: an instance of a class the package
itself declares beneath an iiRDS class *is* an instance of that class.

```python
from iirds import IIRDS
with iirds.open("docs.iirds") as pkg:
    for topic in pkg.instances_of(IIRDS["Topic"]):
        print(pkg.label_of(topic))
```

The closure walks only the package's own `rdfs:subClassOf` declarations —
no ontology is bundled, so `instances_of(IIRDS["InformationUnit"])`
returns only what the package declares beneath it. One rdflib trap worth
knowing: `Namespace` subclasses `str`, so `IIRDS.format` is `str.format`;
**always use bracket syntax** (`IIRDS["format"]`).

**Deliberately not here** (so nobody waits for it): per-class conveniences
(`topics()` is `instances_of(IIRDS["Topic"])` and 25 siblings would drift),
`typed_exactly` (one rdflib call on the public graph), a bundled ontology,
SPARQL wrappers, and anything that returns a verdict.

## Files behind renditions

`pkg.source_of(node)` resolves a Rendition's `iirds:source` to the entry
it names — the helper a naive implementation gets wrong. The core
normalisation matches the validator verbatim (leading slashes stripped,
`./` and internal `../` collapsed); folding backslashes and refusing a
path that escapes the package are the SDK's own added strictness.
`pkg.open(node)` returns a readable stream over that entry (streaming —
a two-gigabyte PDF is read, not loaded), raising when the node names
nothing or names an absent entry. The stream borrows the package's open
ZIP handle, so consume it while the `Package` is still open rather than
after `close()`. Resolution never judges existence: that split keeps
"what does this rendition say" apart from "is this package whole", which
is the validator's question.

## Writing metadata

`write_metadata(graph, destination=None)` serialises a graph as
`metadata.rdf` — and self-verifies: the bytes are parsed back through
the same guarded reader every consumer uses and compared isomorphically
before being handed over, so "the validator can read what the SDK wrote"
is enforced at write time. Byte-stable across repeated writes of the
same Graph object, and no more: rdflib mints blank-node labels from a
process-global counter, so even identically-built graphs serialise
apart, and canonicalisation would be a different, heavier promise.
Composes with `pack()`: write the metadata into a directory, pack the
directory, open the result.

## Untrusted input

A package arrives from a supplier, so `open()` treats its metadata the way
[iirds-validate](https://github.com/dev365code/iirds-validate) does — same
guards, same error strings, shared code:

- XML entity declarations are refused (a tame one is indistinguishable from
  the geometric kind until the parser is already inside it),
- metadata above 64 MiB uncompressed is refused *before* being read,
- a JSON-LD `@context` that names something to fetch is refused wherever it
  nests — a URL, an `@import`, a scoped context on a term, and equally a
  bare name, which the parser would otherwise resolve against whatever
  directory the tool was run from and read off your disk. Reading a package
  touches neither the network nor anything outside the container,
- a byte order mark decides the encoding, as XML says it should.

`META-INF/metadata.jsonld` is read and merged beside `metadata.rdf`
(isomorphic sources count once — blank nodes double under naive union).
`pkg.metadata_sources`, `pkg.metadata_graphs` and `pkg.parse_errors` say
what parsed, which document said what, and what was refused. `pkg.graph`
raises only when *nothing* parsed, because an empty graph is also what a
real, sparse package looks like.

## API stability

0.x: the surface will grow; what is published is intended not to break.
The API is small on purpose — additions are cheap, retractions are not.

## Licence

Apache-2.0, © 2026 Wooyong Lee. Contributions need a `Signed-off-by` line
(DCO); see the workflow in `.github/`.
