Skip to content

Models API

Pydantic models for Digital Product Passport entities.

DigitalProductPassport

The main passport model representing a W3C Verifiable Credential.

dppvalidator.models.DigitalProductPassport

Bases: UNTPBaseModel

Digital Product Passport as a Verifiable Credential.

Root model for UNTP DPP v0.6.1, combining DigitalProductPassport and VerifiableCredential types per W3C VC v2 specification.

Source code in src/dppvalidator/models/passport.py
class DigitalProductPassport(UNTPBaseModel):
    """Digital Product Passport as a Verifiable Credential.

    Root model for UNTP DPP v0.6.1, combining DigitalProductPassport
    and VerifiableCredential types per W3C VC v2 specification.
    """

    _jsonld_type: ClassVar[list[str]] = ["DigitalProductPassport", "VerifiableCredential"]

    context: Annotated[
        list[str],
        Field(
            alias="@context",
            default=[
                "https://www.w3.org/ns/credentials/v2",
                "https://test.uncefact.org/vocabulary/untp/dpp/0.6.1/",
            ],
            description="JSON-LD context URIs",
        ),
    ]
    id: Annotated[
        FlexibleUri,
        Field(..., description="Unique identifier (URI) for this passport"),
    ]
    issuer: CredentialIssuer = Field(..., description="Organisation issuing this credential")
    valid_from: Annotated[
        datetime | None,
        Field(default=None, alias="validFrom", description="Credential validity start"),
    ]
    valid_until: Annotated[
        datetime | None,
        Field(default=None, alias="validUntil", description="Credential expiry date"),
    ]
    credential_subject: Annotated[
        ProductPassport | None,
        Field(
            default=None,
            alias="credentialSubject",
            description="The product passport content",
        ),
    ]
    credential_status: Annotated[
        CredentialStatus | list[CredentialStatus] | None,
        Field(
            default=None,
            alias="credentialStatus",
            description="Credential revocation/suspension status per W3C VC v2",
        ),
    ]

    @model_validator(mode="after")
    def validate_dates(self) -> DigitalProductPassport:
        """Ensure validFrom is before validUntil if both are present."""
        if self.valid_from and self.valid_until and self.valid_from >= self.valid_until:
            raise ValueError("validFrom must be before validUntil")
        return self

    @model_validator(mode="after")
    def validate_material_mass_fractions(self) -> DigitalProductPassport:
        """Validate material mass fractions don't exceed 1.0.

        Per UNTP spec, mass fractions can be partial declarations (sum < 1.0).
        Only sum > 1.0 is physically impossible and should error.
        Semantic validation checks for exact sum when appropriate.
        """
        if not self.credential_subject:
            return self
        materials = self.credential_subject.materials_provenance
        if not materials:
            return self
        fractions = [m.mass_fraction for m in materials if m.mass_fraction is not None]
        if fractions:
            total = sum(fractions)
            if total > 1.01:  # Allow small tolerance for floating point
                raise ValueError(f"Material mass fractions cannot exceed 1.0, got {total:.3f}")
        return self

validate_dates()

Ensure validFrom is before validUntil if both are present.

Source code in src/dppvalidator/models/passport.py
@model_validator(mode="after")
def validate_dates(self) -> DigitalProductPassport:
    """Ensure validFrom is before validUntil if both are present."""
    if self.valid_from and self.valid_until and self.valid_from >= self.valid_until:
        raise ValueError("validFrom must be before validUntil")
    return self

validate_material_mass_fractions()

Validate material mass fractions don't exceed 1.0.

Per UNTP spec, mass fractions can be partial declarations (sum < 1.0). Only sum > 1.0 is physically impossible and should error. Semantic validation checks for exact sum when appropriate.

Source code in src/dppvalidator/models/passport.py
@model_validator(mode="after")
def validate_material_mass_fractions(self) -> DigitalProductPassport:
    """Validate material mass fractions don't exceed 1.0.

    Per UNTP spec, mass fractions can be partial declarations (sum < 1.0).
    Only sum > 1.0 is physically impossible and should error.
    Semantic validation checks for exact sum when appropriate.
    """
    if not self.credential_subject:
        return self
    materials = self.credential_subject.materials_provenance
    if not materials:
        return self
    fractions = [m.mass_fraction for m in materials if m.mass_fraction is not None]
    if fractions:
        total = sum(fractions)
        if total > 1.01:  # Allow small tolerance for floating point
            raise ValueError(f"Material mass fractions cannot exceed 1.0, got {total:.3f}")
    return self

options: show_source: false members: - id - type - issuer - valid_from - valid_until - credential_subject

CredentialIssuer

The issuer of a Digital Product Passport.

dppvalidator.models.CredentialIssuer

Bases: UNTPStrictModel

Issuer of a verifiable credential.

Source code in src/dppvalidator/models/credential.py
class CredentialIssuer(UNTPStrictModel):
    """Issuer of a verifiable credential."""

    _jsonld_type: ClassVar[list[str]] = ["CredentialIssuer"]

    id: Annotated[
        FlexibleUri,
        Field(..., description="W3C DID of the issuer (did:web, did:webvh, or https URL)"),
    ]
    name: str = Field(..., description="Name of the issuer person or organisation")
    issuer_also_known_as: Annotated[
        list[Party] | None,
        Field(
            default=None,
            alias="issuerAlsoKnownAs",
            description="Other registered identifiers for this issuer",
        ),
    ]

options: show_source: false

Product

Product information within a passport.

dppvalidator.models.Product

Bases: UNTPBaseModel

Product information including identification and manufacturer details.

Source code in src/dppvalidator/models/product.py
class Product(UNTPBaseModel):
    """Product information including identification and manufacturer details."""

    _jsonld_type: ClassVar[list[str]] = ["Product"]

    id: Annotated[
        FlexibleUri,
        Field(..., description="Globally unique ID of the product as a URI"),
    ]
    name: str = Field(..., description="Registered name of the product")
    registered_id: Annotated[
        str | None,
        Field(
            default=None,
            alias="registeredId",
            description="Registration number within the register",
        ),
    ]
    id_scheme: Annotated[
        IdentifierScheme | None,
        Field(default=None, alias="idScheme", description="Identifier scheme for this product"),
    ]
    batch_number: Annotated[
        str | None,
        Field(default=None, alias="batchNumber", description="Production batch identifier"),
    ]
    product_image: Annotated[
        Link | None,
        Field(default=None, alias="productImage", description="Reference to product image"),
    ]
    description: str | None = Field(default=None, description="Textual product description")
    characteristics: Characteristics | None = Field(
        default=None, description="Industry-specific characteristics"
    )
    product_category: Annotated[
        list[Classification] | None,
        Field(default=None, alias="productCategory", description="Product classification codes"),
    ]
    further_information: Annotated[
        list[Link] | None,
        Field(default=None, alias="furtherInformation", description="Additional information links"),
    ]
    produced_by_party: Annotated[
        Party | None,
        Field(default=None, alias="producedByParty", description="Manufacturing party"),
    ]
    produced_at_facility: Annotated[
        Facility | None,
        Field(default=None, alias="producedAtFacility", description="Manufacturing facility"),
    ]
    production_date: Annotated[
        date | None,
        Field(default=None, alias="productionDate", description="ISO 8601 production date"),
    ]
    country_of_production: Annotated[
        str | None,
        Field(
            default=None,
            alias="countryOfProduction",
            description="ISO 3166-1 country code of production",
        ),
    ]
    serial_number: Annotated[
        str | None,
        Field(default=None, alias="serialNumber", description="Serialised item identifier"),
    ]
    dimensions: Dimension | None = Field(default=None, description="Physical dimensions")

options: show_source: false

ProductPassport

The credential subject containing product passport data.

dppvalidator.models.ProductPassport

Bases: UNTPBaseModel

Product passport credential subject.

Source code in src/dppvalidator/models/credential.py
class ProductPassport(UNTPBaseModel):
    """Product passport credential subject."""

    _jsonld_type: ClassVar[list[str]] = ["ProductPassport"]

    id: Annotated[
        FlexibleUri | None,
        Field(default=None, description="Identifier for the credential subject (URI)"),
    ]
    product: Product | None = Field(default=None, description="Product information")
    granularity_level: Annotated[
        GranularityLevel | None,
        Field(
            default=None,
            alias="granularityLevel",
            description="Item, batch, or model level passport",
        ),
    ]
    conformity_claim: Annotated[
        list[Claim] | None,
        Field(
            default=None,
            alias="conformityClaim",
            description="Conformity claims about the product",
        ),
    ]
    emissions_scorecard: Annotated[
        EmissionsPerformance | None,
        Field(
            default=None,
            alias="emissionsScorecard",
            description="Emissions performance scorecard",
        ),
    ]
    traceability_information: Annotated[
        list[TraceabilityPerformance] | None,
        Field(
            default=None,
            alias="traceabilityInformation",
            description="Traceability events by value chain process",
        ),
    ]
    circularity_scorecard: Annotated[
        CircularityPerformance | None,
        Field(
            default=None,
            alias="circularityScorecard",
            description="Circularity performance scorecard",
        ),
    ]
    due_diligence_declaration: Annotated[
        Link | None,
        Field(
            default=None,
            alias="dueDiligenceDeclaration",
            description="Due diligence declaration link",
        ),
    ]
    materials_provenance: Annotated[
        list[Material] | None,
        Field(
            default=None,
            alias="materialsProvenance",
            description="Material origin and mass fraction information",
        ),
    ]

options: show_source: false

Measure

A measurement with value and unit.

dppvalidator.models.Measure

Bases: UNTPStrictModel

Numeric value with unit of measure (UNECE Rec20).

Source code in src/dppvalidator/models/primitives.py
class Measure(UNTPStrictModel):
    """Numeric value with unit of measure (UNECE Rec20)."""

    _jsonld_type: ClassVar[list[str]] = ["Measure"]

    value: float = Field(..., description="The numeric value of the measure")
    unit: str = Field(
        ...,
        description="Unit of measure from UNECE Rec20 (e.g., KGM, LTR, EA)",
    )

options: show_source: false

Usage Example

from dppvalidator.models import (
    DigitalProductPassport,
    CredentialIssuer,
    Product,
    Measure,
)

# Create a passport
passport = DigitalProductPassport(
    id="https://example.com/dpp/001",
    issuer=CredentialIssuer(id="https://example.com/issuer", name="Acme Corp"),
)

# Access fields
print(passport.id)
print(passport.issuer.name)

# Serialize to dict
data = passport.model_dump(by_alias=True)

# Serialize to JSON
json_str = passport.model_dump_json(by_alias=True)