Skip to content

Commit 437c74c

Browse files
authored
feat: carry the MAP 0.3 artifacts and release 0.3.0 (#10)
1 parent 1b3dcc9 commit 437c74c

18 files changed

Lines changed: 732 additions & 2885 deletions

‎README.md‎

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,18 @@
1-
# MailSchema for Python
1+
# mailschema
22

3-
[![PyPI](https://img.shields.io/pypi/v/mailschema)](https://pypi.org/project/mailschema/)
4-
[![CI](https://github.com/mailschema/python/actions/workflows/test.yml/badge.svg)](https://github.com/mailschema/python/actions/workflows/test.yml)
5-
6-
The Mail Action Protocol 0.2 core artifacts, and Draft 2020-12 validation of MailSchema Registry contributions, with no network requests.
7-
8-
[Specification](https://mailschema.org/specification) · [Registry](https://mailschema.org/registry) · [Tools](https://mailschema.org/tools) · [Source](https://github.com/mailschema/python)
9-
10-
## Install
3+
The [Mail Action Protocol](https://mailschema.org) 0.3 artifacts for Python, byte for byte as mailschema.org publishes them: the profile record, the JSON-LD context, and the core, type contract and implementation record schemas. The profile record binds the context and the first two schemas by SHA-256. The package has no dependencies.
114

125
```sh
13-
pip install mailschema
14-
```
15-
16-
Python 3.10 or newer is required.
17-
18-
## MAP 0.2 core artifacts
19-
20-
```python
21-
from mailschema import MAP_PROFILE, get_contract_format_schema, get_forms_schema, get_map_context, get_map_schema
6+
pip install mailschema==0.3.0
227
```
238

24-
Each returns a fresh copy of the file the [profile record](https://mailschema.org/profiles/map/0.2.json) binds by SHA-256. Type contracts are not bundled: a client obtains them from the [Registry catalogue](https://mailschema.org/registry/catalog.json) by digest. Processing MAP messages is outside this package; see the [profile](https://mailschema.org/specification/profile).
25-
26-
## Registry data
27-
289
```python
29-
from mailschema import contribution_errors, validate_contribution
10+
import json
11+
import mailschema
3012

31-
errors = contribution_errors(candidate)
32-
validate_contribution(candidate) # Raises ValueError for invalid input.
33-
```
34-
35-
`validate_record`, `record_errors` and `get_record_schema` handle expanded Registry records.
36-
37-
```sh
38-
python -m mailschema check contribution.json
39-
python -m mailschema check record.json --record
40-
python -m mailschema schema --map
13+
core_schema = json.loads(mailschema.artifact("core.schema.json"))
4114
```
4215

43-
## Trust boundary
16+
`mailschema.ARTIFACTS` lists the bundled files and `mailschema.PROFILE` names the profile. To parse and check descriptions and contracts, use the JavaScript or Ruby package, or implement the [specification](https://mailschema.org/specification/core); schema validation alone does not establish a valid description.
4417

45-
A valid document is structured input. Validation does not authenticate a service, grant authority or establish product conformance. The CLI reads one local file of at most 256 KiB and does not upload or modify it. Package versions and MAP profile versions advance independently. MIT licensed.
18+
Source: [mailschema/python](https://github.com/mailschema/python). License: MIT.

‎UPSTREAM.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# Release provenance
22

3-
Version `0.2.1` derives from [`mailschema/mailschema@db99c15`](https://github.com/mailschema/mailschema/commit/db99c1582f68b3cd3e80d5fe6c029c8535bf470c).
3+
Version `0.3.0` derives from [`mailschema/mailschema@2c26469`](https://github.com/mailschema/mailschema/commit/2c26469209070f04ff9f8ec29e1387b8699a46a0). Its sources, tests and bundled files are prepared from that commit by `npm run packages:prepare`.
44

5-
The bundled files are exact projections of that source commit and do not independently define MAP:
5+
The bundled MAP 0.3 artifacts are exact copies of that commit's files and do not independently define MAP:
66

7-
- MAP 0.2 core schema: `732d60fcb758d945dc15c57d885c9d1c0c76423a7439096d9755e8ec9830836a`
8-
- MAP 0.2 JSON-LD context: `dfed0189524d7d16d31ed3d8af97e9a0c458200c4c034b413f9d14fa35b60257`
9-
- Type contract format: `e030007cc034662c9d9d448a8a035aff3106cfb96fb60e6aa9f92b99b25df0b9`
10-
- Form fields block: `3a976b93a057380e3630ca05e77e221a43a98559fb7fda8066299ec4bf2a8f9e`
11-
- Contribution schema: `a8a241b4837971a6251bcc135c704283536f4fb825fbbaedaf1598c6e688d564`
7+
- profile (`mailschema/profile.json`): `d9481627f855c127a4f5d6ddb7f3b699f66f6475bc8672d609f5b499f83d1a0f`
8+
- context (`mailschema/context.jsonld`): `a51490d1fe8af695c14ea508aad88fd0429d18564791b6b52f3ab07d5c51aab3`
9+
- core-schema (`mailschema/core.schema.json`): `0ad09a6955a47e7a2c1c1eec0747a3e0b15c08a79860e90ce4e65da11983aeb3`
10+
- contract-schema (`mailschema/contract.schema.json`): `86bf057529c4fd2c7f0a880825774afeed31e9f7ba1d99760858a585fe28d18d`
11+
- implementation-schema (`mailschema/implementation.schema.json`): `c62acc3ebb9f384c8fba7be353ab1905b65883ac2b16b147fd98485eec643396`
1212

13-
The canonical artifacts live in the main MailSchema repository. This repository owns the Python package surface and its release history.
13+
The canonical artifacts live in the main MailSchema repository. This repository owns the Python package and its release history.

‎pyproject.toml‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "mailschema"
7-
version = "0.2.1"
8-
description = "Mail Action Protocol 0.2 core artifacts and MailSchema Registry validation"
7+
version = "0.3.0"
8+
description = "Mail Action Protocol 0.3 artifacts: the profile record, context and schemas"
99
readme = "README.md"
1010
license = "MIT"
1111
license-files = ["LICENSE"]
1212
requires-python = ">=3.10"
1313
authors = [{ name = "MailSchema contributors" }]
14-
dependencies = ["jsonschema[format]>=4.23,<5"]
14+
dependencies = []
1515
classifiers = [
1616
"Development Status :: 3 - Alpha",
1717
"Programming Language :: Python :: 3",
@@ -23,8 +23,5 @@ Homepage = "https://mailschema.org/tools"
2323
Repository = "https://github.com/mailschema/python"
2424
Issues = "https://github.com/mailschema/python/issues"
2525

26-
[project.scripts]
27-
mailschema = "mailschema.__main__:main"
28-
2926
[tool.hatch.build.targets.wheel]
3027
packages = ["src/mailschema"]

‎src/mailschema/__init__.py‎

Lines changed: 23 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,31 @@
1-
"""The MAP 0.2 core artifacts and JSON Schema validation for MailSchema Registry contributions."""
1+
"""Mail Action Protocol 0.3 artifacts, byte for byte as mailschema.org publishes them.
22
3-
import json
4-
from importlib.resources import files
5-
from typing import Any
6-
7-
from jsonschema import Draft202012Validator, FormatChecker
8-
9-
__version__ = "0.2.1"
10-
11-
MAP_PROFILE = "https://mailschema.org/profiles/map/0.2"
12-
"""The MAP profile whose core artifacts this package carries."""
13-
14-
15-
def _bundled(name: str) -> dict[str, Any]:
16-
return json.loads(files(__package__).joinpath(name).read_text(encoding="utf-8"))
17-
18-
19-
def get_map_schema() -> dict[str, Any]:
20-
"""Return a fresh copy of the MAP 0.2 core schema, as the profile record binds it by SHA-256."""
21-
return _bundled("map-0.2.schema.json")
22-
23-
24-
def get_map_context() -> dict[str, Any]:
25-
"""Return a fresh copy of the MAP 0.2 JSON-LD context."""
26-
return _bundled("map-0.2.jsonld")
27-
28-
29-
def get_contract_format_schema() -> dict[str, Any]:
30-
"""Return a fresh copy of the type contract format every MAP 0.2 contract follows."""
31-
return _bundled("type-contract-0.2.schema.json")
3+
The profile record, the JSON-LD context, and the core, type contract and implementation record
4+
schemas. The profile record binds the context and the first two schemas by SHA-256.
5+
"""
326

7+
from importlib.resources import files
338

34-
def get_forms_schema() -> dict[str, Any]:
35-
"""Return a fresh copy of the form fields block contracts pin."""
36-
return _bundled("forms-0.1.schema.json")
37-
38-
39-
def get_contribution_schema() -> dict[str, Any]:
40-
"""Return a fresh copy of the bundled Registry contribution schema."""
41-
return _bundled("contribution.schema.json")
42-
43-
44-
def get_record_schema() -> dict[str, Any]:
45-
"""Return the schema for expanded Registry records."""
46-
schema = get_contribution_schema()
47-
return {"$schema": schema["$schema"], "$defs": schema["$defs"], "$ref": "#/$defs/record"}
48-
49-
50-
_schema = get_contribution_schema()
51-
_validator = Draft202012Validator(_schema, format_checker=FormatChecker())
52-
_variants = {
53-
variant["properties"]["kind"]["const"]: Draft202012Validator(
54-
{"$schema": _schema["$schema"], "$defs": _schema["$defs"], **variant},
55-
format_checker=FormatChecker(),
56-
)
57-
for variant in _schema["oneOf"]
58-
}
59-
_record_validator = Draft202012Validator(get_record_schema(), format_checker=FormatChecker())
60-
61-
62-
def _errors(validator: Draft202012Validator, value: Any) -> list[str]:
63-
result = []
64-
for error in validator.iter_errors(value):
65-
path = "/" + "/".join(str(part).replace("~", "~0").replace("/", "~1") for part in error.absolute_path)
66-
result.append(f"{path} {error.message}")
67-
return result
68-
69-
70-
def contribution_errors(value: Any) -> list[str]:
71-
"""Return structural errors; an empty list means the schema checks passed."""
72-
kind = value.get("kind") if isinstance(value, dict) else None
73-
validator = _variants.get(kind, _validator) if isinstance(kind, str) else _validator
74-
return _errors(validator, value)
75-
9+
__version__ = "0.3.0"
7610

77-
def record_errors(value: Any) -> list[str]:
78-
"""Return structural errors for a Registry record."""
79-
return _errors(_record_validator, value)
11+
PROFILE = "https://mailschema.org/profiles/map/0.3"
12+
"""The MAP profile these artifacts define."""
8013

14+
CONTEXT = "https://mailschema.org/contexts/map-0.3.jsonld"
15+
"""The JSON-LD context every MAP 0.3 description names."""
8116

82-
def validate_contribution(value: Any) -> None:
83-
"""Raise ValueError if a contribution does not match the schema."""
84-
errors = contribution_errors(value)
85-
if errors:
86-
raise ValueError("Invalid contribution:\n" + "\n".join(errors))
17+
ARTIFACTS = (
18+
"profile.json",
19+
"context.jsonld",
20+
"core.schema.json",
21+
"contract.schema.json",
22+
"implementation.schema.json",
23+
)
24+
"""The bundled files, by name."""
8725

8826

89-
def validate_record(value: Any) -> None:
90-
"""Raise ValueError if a Registry record does not match the schema."""
91-
errors = record_errors(value)
92-
if errors:
93-
raise ValueError("Invalid type record:\n" + "\n".join(errors))
27+
def artifact(name: str) -> bytes:
28+
"""Return the exact bytes of a bundled artifact."""
29+
if name not in ARTIFACTS:
30+
raise KeyError(f"mailschema: unknown artifact {name!r}")
31+
return files(__package__).joinpath(name).read_bytes()

‎src/mailschema/__main__.py‎

Lines changed: 0 additions & 57 deletions
This file was deleted.

‎src/mailschema/context.jsonld‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"@context": {
3+
"@version": 1.1,
4+
"@protected": true,
5+
"@vocab": "https://mailschema.org/vocab/map/0.3#",
6+
"MailAction": "https://mailschema.org/vocab/map/0.3#MailAction",
7+
"profile": {
8+
"@id": "profile",
9+
"@type": "@id"
10+
},
11+
"type": {
12+
"@id": "type",
13+
"@type": "@json"
14+
},
15+
"service": {
16+
"@id": "service",
17+
"@type": "@json"
18+
},
19+
"subject": {
20+
"@id": "subject",
21+
"@type": "@json"
22+
},
23+
"terms": {
24+
"@id": "terms",
25+
"@type": "@json"
26+
},
27+
"details": {
28+
"@id": "details",
29+
"@type": "@json"
30+
},
31+
"operations": {
32+
"@id": "operations",
33+
"@type": "@json"
34+
},
35+
"human": {
36+
"@id": "human",
37+
"@type": "@json"
38+
},
39+
"recipient": "recipient",
40+
"inLanguage": "inLanguage",
41+
"issuedAt": {
42+
"@id": "issuedAt",
43+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
44+
},
45+
"expiresAt": {
46+
"@id": "expiresAt",
47+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)