Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [main]
branches: [main, feat/final-java-parity-migration]
pull_request:
branches: [main]

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/mock-services-demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
python -m build "$DEMO_DIR" --outdir "$DEMO_DIR/dist"
python -m venv .mock-services-demo-venv
.mock-services-demo-venv/bin/python -m pip install \
dist/dpp_sdk-0.4.0-py3-none-any.whl \
dist/dpp_sdk-0.5.0-py3-none-any.whl \
"$DEMO_DIR"/dist/dpp_sdk_mock_services_demo-0.1.0-py3-none-any.whl
.mock-services-demo-venv/bin/python -m pip check

Expand All @@ -58,7 +58,7 @@ jobs:
run: |
mkdir -p artifacts
outside="$(mktemp -d)"
sdk_wheel="$GITHUB_WORKSPACE/dist/dpp_sdk-0.4.0-py3-none-any.whl"
sdk_wheel="$GITHUB_WORKSPACE/dist/dpp_sdk-0.5.0-py3-none-any.whl"
report="$GITHUB_WORKSPACE/artifacts/verification-report-${{ inputs.profile }}.json"
set +e
if [ "${{ inputs.profile }}" = "0.4.0" ]; then
Expand Down
117 changes: 107 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,105 @@ All notable changes to `dpp-sdk` are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - Synchronisation with java-dpp-sdk
## [0.5.0] - 2026-10-30


### Added
- A canonical `DigitalProductPassport` model with a `DppHeader` and typed DataElement variants,
plus FULL and COMPRESSED JSON codecs and explicit semantic, format, and identifier validation.
- A typed DPP4Fun profile over the core passport, including its bundled 488-definition semantic catalog,
typed aspects and Characteristics variants, reusable composites, profile validation, and transport codec.
- Generic JSON repository and registry clients that accept JSON-compatible request values and return
response envelopes without owning domain codecs or validation.

### Changed
- The core model and furniture profile now use the final passport structure and current profile
semantics; the earlier `Dpp`/`DppCore` and flat furniture model shapes are no longer accepted as
current model APIs.
- Client methods return neutral response DTOs. Repository payloads are exposed through
`DppApiResponse.payload`; clients do not parse them into caller-selected model types.
- Public model and client attributes use Python `snake_case` while JSON aliases retain the API's
canonical field names.
- Repository and registry clients require explicit service base URLs. Callers own model parsing,
serialization, and validation.

### Fixed
- Reject malformed HTTP base URLs before sending a request, and require `messageType` and `text`
for each API message while allowing the outer `message` list to be absent or empty.
- Preserve numeric DataElement equality and hashing across nested integer, float, and Decimal values.
- Apply the approved DPP4Fun profile bindings, unit handling, and semantic-resolution behavior.

### Documentation
- Added a generated DPP4Fun field reference, a guide for building your own profile on Core, a
standards and validation-rules page, and a contributing guide.
- Reorganized the model guide around tasks (build, read, edit, validate, convert) and documented
the read API (`typed_aspect()`, `field()`, `quantity()`, `selected_characteristics()`).
- The README quickstart now reads the passport back as a typed object and lists the exceptions.

### Breaking changes and migration

The released `v0.4.0` repository client accepted a model codec and validator and returned typed model
objects. The `0.5.0` client takes only a service URL (and optionally a caller-supplied HTTPX client),
accepts JSON-compatible values, and returns response envelopes. Move validation and codec work to the
application boundary:

```python
# v0.4.0
repo = DppRepoClient(base_url, codec, validate_dpp4fun)
created = repo.create_dpp(passport)
loaded = repo.read_dpp_by_id(created.dppId)

# 0.5.0
import json
from dpp_sdk import Dpp4FunJsonCodec, validate_dpp4fun
from dpp_sdk.clients import DppRepresentation, DppRepoClient

codec = Dpp4FunJsonCodec()
validation = validate_dpp4fun(passport)
if not validation.is_valid:
raise ValueError(validation.issues)
with DppRepoClient(base_url) as repo:
created = repo.create_dpp(json.loads(codec.to_json(passport)))
if created.digital_product_passport_id is None:
raise RuntimeError("repository did not return a passport ID")
response = repo.read_dpp_by_id(
created.digital_product_passport_id,
DppRepresentation.FULL,
)
loaded = codec.from_json(json.dumps(response.payload))
```

`DppApiResponse` carries `status_code`, `payload`, and `message`. API failures raise
`DppApiClientError`; malformed JSON or payload mapping raises `DppMappingClientError`; unstructured
HTTP failures and transport failures use `DppHttpClientError` and `DppNetworkClientError`. Semantic
validation is performed explicitly before a request with the relevant core or DPP4Fun validator.

The former `Dpp`/`DppCore` model family and flat furniture classes were replaced by
`DigitalProductPassport`, `DppHeader`, five DataElement variants, and a DPP4Fun aggregate over the
canonical passport. Python object attributes also changed from camelCase to `snake_case`; for
example, `dpp.passportMetadata.uniqueProductIdentifier` is now
`passport.dpp_header.unique_product_identifier`, while the JSON key remains
`uniqueProductIdentifier`. Map stored legacy fields to the current model explicitly; no general
legacy model converter is provided. Use `DppJsonCodec.to_expanded_json()` or
`DppJsonCodec.to_compressed_json()` for core transport, and `Dpp4FunJsonCodec` for typed profile
views. FULL responses can be decoded with the codec; COMPRESSED responses may need prior metadata or
semantic resolution to restore typed details.

The `for_local_mock()` factories, default endpoint helpers/constants, `health_check()`, typed
`read_compressed_dpp_by_id()`, product-ID historical-read helper, the `dpp_sdk.clients.DppCodec`
and `dpp_sdk.clients.DppValidator` typing protocols, `DppValidationClientError`, and the
`UpdateDataElementRequest` compatibility DTO are not part of the current client API. (The model
validator `dpp_sdk.core.DppValidator` is unaffected.) Pass local service URLs directly, check service health independently, use
`read_dpp_version_by_id_and_date()` for supported history reads, and pass direct JSON-compatible
DataElement bodies to fine-grained update methods.

### Compatibility
- The generic JSON client API is a breaking change from the released typed-client API; see the
migration examples above.
- The legacy product-ID history route and legacy registry request aliases are not part of the
current public Python client surface.

## [0.4.0] - 2026-08-11

### Added
- An isolated `examples/mock-services-demo` consumer project with reproducible Mock image
Expand Down Expand Up @@ -46,8 +144,6 @@ All notable changes to `dpp-sdk` are documented here. The format is based on
boundary, while direct defensive validators retain indexed semantic errors.

### Changed
- Active model guidance and contract tests consistently use `with_updates()` as the
revalidating immutable-edit seam; intentional Pydantic bypass probes remain internal.
- `README.md`: full lifecycle Quickstart (build → validate → store → register → read → update →
delete) now uses application-provided endpoints and no-network examples.
- Domain models expose `with_updates()` for revalidated immutable updates; contracted collections
Expand All @@ -71,9 +167,8 @@ All notable changes to `dpp-sdk` are documented here. The format is based on

## [0.2.1] — Previous package baseline

- Package metadata and `dpp_sdk.__version__` identified the preceding checkout as `0.2.1`.
- This repository checkout contains no local release tag for `0.2.1`; commit history remains
the provenance source for changes predating the current unreleased section.
- Package metadata and `dpp_sdk.__version__` identified version `0.2.1`.
- Commit history remains the provenance source for changes predating the `0.2.1` baseline.

## [0.1.1]

Expand All @@ -90,7 +185,9 @@ First public release of the Python port of the Mock DPP SDK.
- `dpp_sdk.clients` — `httpx`-based HTTP clients for the DPP repository and registry APIs.
- Packaging: PEP 621 `pyproject.toml`, `src/` layout, `py.typed` marker, sdist + wheel build.

[Unreleased]: https://github.com/CIR4FUN-EU/dpp-sdk-python/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/CIR4FUN-EU/dpp-sdk-python/releases/tag/v0.4.0
[0.1.1]: https://github.com/CIR4FUN-EU/dpp-sdk-python/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/CIR4FUN-EU/dpp-sdk-python/releases/tag/v0.1.0
[Unreleased]: https://github.com/CIR4FUN-EU/python-dpp-sdk/compare/v0.5.0...main
[0.5.0]: https://github.com/CIR4FUN-EU/python-dpp-sdk/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/CIR4FUN-EU/python-dpp-sdk/compare/v0.2.1...v0.4.0
[0.2.1]: https://github.com/CIR4FUN-EU/python-dpp-sdk/compare/v0.1.1...v0.2.1
[0.1.1]: https://github.com/CIR4FUN-EU/python-dpp-sdk/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/CIR4FUN-EU/python-dpp-sdk/releases/tag/v0.1.0
130 changes: 130 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Contributing

This page is for people working on the SDK itself. To use the SDK, start with the
[README](README.md).

## Set up the development environment

**Run from:** the repository root (the folder that contains `pyproject.toml`).

```bash
git clone https://github.com/CIR4FUN-EU/python-dpp-sdk.git
cd python-dpp-sdk
python -m venv .venv
```

Activate the environment. Do this once in every new terminal:

- Windows (PowerShell): `.venv\Scripts\Activate.ps1`. If PowerShell blocks it, run
`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once.
- Linux/macOS: `source .venv/bin/activate`. If `python` is not found, use `python3`.

Then install the SDK with its development tools and run the tests:

```bash
python -m pip install -e ".[dev,release]"
python -m pytest
```

## Checks

Run these before you open a pull request. CI runs the same commands on Python 3.11 and 3.12.

```bash
python -m ruff check .
python -m ruff format --check .
python -m mypy
python -m pytest
```

Focused test sets, all without running services (the clients tests use `httpx.MockTransport`):

| Area | Command |
| --- | --- |
| Core model and validation | `python -m pytest tests/test_core_model.py tests/test_core_json_codec.py tests/test_core_validation_parity.py` |
| DPP4Fun profile | `python -m pytest tests/test_dpp4fun_validation.py tests/test_transport_roundtrip.py` |
| HTTP clients | `python -m pytest tests/test_clients.py tests/test_end_to_end.py` |
| Documentation | `python -m pytest tests/test_documentation.py tests/test_readme_examples.py` |

Live tests against running services are opt-in; see
[ADVANCED_OPERATIONS.md](examples/mock-services-demo/ADVANCED_OPERATIONS.md).

## Documentation

- `README.md` is also the PyPI project page. Use absolute `https://` links there; PyPI cannot
resolve relative links.
- The README and guides contain no version numbers; a test enforces this.
- The Python code blocks in `README.md`, `docs/model-guide.md`, and `docs/extending.md` are executed
by the documentation tests. Keep them runnable.
- `docs/dpp4fun-fields.md` is generated. After changing the DPP4Fun semantic profile, regenerate it:

```bash
python scripts/generate_dpp4fun_fields.py
```

A test fails when the committed file is out of date.

## Architecture diagrams

Each maintained consumer diagram has an editable Mermaid source and a sibling SVG preview under
`docs/architecture/`. The render script uses the pinned Mermaid CLI image
`ghcr.io/mermaid-js/mermaid-cli/mermaid-cli:11.4.2` (digest
`sha256:99c983b3ab4e14033f2880bc1b9de17e5090b4515dabd63fe9cf8c0ae6130956`). Docker must be able to
pull that image before rendering.

Run from the repository root:

```powershell
powershell -ExecutionPolicy Bypass -File scripts/render_diagrams.ps1
powershell -ExecutionPolicy Bypass -File scripts/render_diagrams.ps1 -VerifyOnly
```

```bash
pwsh -ExecutionPolicy Bypass -File scripts/render_diagrams.ps1
pwsh -ExecutionPolicy Bypass -File scripts/render_diagrams.ps1 -VerifyOnly
```

`-VerifyOnly` checks that each source has an SVG and does not render or modify assets. Review SVG
diffs after rendering. The published diagram pairs are:

| Diagram | Mermaid source | SVG preview |
|---|---|---|
| SDK overview | [python-sdk-overview.mmd](docs/architecture/python-sdk-overview.mmd) | [python-sdk-overview.svg](docs/architecture/python-sdk-overview.svg) |
| Client request flow | [python-client-request-flow.mmd](docs/architecture/python-client-request-flow.mmd) | [python-client-request-flow.svg](docs/architecture/python-client-request-flow.svg) |
| Core model | [python-core-model.mmd](docs/architecture/python-core-model.mmd) | [python-core-model.svg](docs/architecture/python-core-model.svg) |
| DPP4Fun model | [python-dpp4fun-model.mmd](docs/architecture/python-dpp4fun-model.mmd) | [python-dpp4fun-model.svg](docs/architecture/python-dpp4fun-model.svg) |

## Mock-service verification

Commands are in [ADVANCED_OPERATIONS.md](examples/mock-services-demo/ADVANCED_OPERATIONS.md).
This section records what the profiles and reports mean.

| Profile (`examples/mock-services-demo/`) | Use | Status |
|---|---|---|
| `compose.yaml` defaults or `.env` | Local consumer profile with the current example Java images | Primary |
| `env/0.5.1.env` | Image-identity verification profile, also the CI default | Maintained |
| `env/pinned.env` | Earlier reproducible tag-plus-digest evidence | Historical |
| `env/0.5.0.env` | Earlier semantic-version evidence | Historical |
| `env/0.4.0.env` | Legacy compatibility evidence; demo commands need `--legacy` | Informational |

Legacy runs are informational and do not satisfy the strict verification gate. Keep their reports
separate from the maintained profile's evidence.

`demo` is the short educational journey. `python -m dpp_mock_services_demo.maintainer
sdk-contracts`, `live`, and `verify` produce the full SDK, service, package, image, and policy
evidence. `full`, `verify`, `integration`, `services`, and `all` remain compatibility commands.
Passing an educational step is evidence for that step only, not a compatibility certification.

The manually triggered GitHub workflow `Mock services demo verification` builds both wheels, starts
a fresh disposable stack for the selected profile, runs installed-wheel verification, captures
evidence, and tears down its project. The PyPI release workflow does not start mock images; it runs
SDK-only installed-wheel verification.

Strict reports use `PASSED`, `EXPECTED_ERROR`, `SKIPPED`, `FAILED`, and `NOT_IMPLEMENTED`. The
educational report uses `PASS`, `EXPECTED_ERROR`, `BLOCKED`, `SKIP`, and `FAIL`. Keep educational
success separate from the strict verification verdict. Clean up reports separately from Compose
projects, and keep the ones you need as evidence.

## Releasing

The release steps are in [RELEASING.md](RELEASING.md).
Loading
Loading