# 34. `example_schema.py` — ASN.1 structure definitions with `synta.schema`
[← Example index](index.md) · [`example_schema.py` on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/example_schema.py)
Bindings: `synta.schema.asn1_sequence`, `synta.schema.asn1_choice`,
`synta.schema.asn1_field`.
- Define a CHOICE type (`Time`) with `@asn1_choice` over `UtcTime` and
`GeneralizedTime`; encode with `to_der()`, decode with `from_der()`, and
verify that the active variant is preserved.
- Define a simple SEQUENCE (`Validity`) with `@asn1_sequence` using the CHOICE
type as a nested field; verify the round-trip and that `@dataclass` `__eq__`
and `__repr__` continue to work.
- Define a SEQUENCE with an optional `[0] EXPLICIT` tagged field and an optional
`[2] IMPLICIT` tagged field using `asn1_field()`; show that absent fields are
omitted from the encoding and decoded back as `None`.
- Define a SEQUENCE with a `List[synta.IA5String]` field; encode a list of two
names, decode back, and verify element count and values.
- Show `ValueError` when encoding a CHOICE instance with all fields `None`.
- Show `ValueError` when encoding a SEQUENCE with a required field set to `None`.
- Show `TypeError` when applying `@asn1_sequence` before `@dataclass`.
## Source
```python
#!/usr/bin/env python3
"""
Example 34: ASN.1 structure definitions with synta.schema.
Demonstrates: asn1_sequence, asn1_choice, asn1_field — define ASN.1 SEQUENCE
and CHOICE types in Python using class decorators and get to_der() / from_der()
automatically.
"""
from __future__ import annotations
import dataclasses
from dataclasses import dataclass, field
from typing import List, Optional
import synta
from synta.schema import asn1_choice, asn1_field, asn1_sequence
def section(title: str) -> None:
print(f"\n{'─' * 60}\n{title}\n{'─' * 60}")
# ---------------------------------------------------------------------------
# Type definitions (shared across demos)
# ---------------------------------------------------------------------------
@asn1_choice
@dataclass
class Time:
"""ASN.1 Time CHOICE — either UTCTime or GeneralizedTime."""
utc_time: synta.UtcTime | None = None
generalized_time: synta.GeneralizedTime | None = None
@asn1_sequence
@dataclass
class Validity:
"""ASN.1 Validity SEQUENCE — two Time values."""
not_before: Time
not_after: Time
@asn1_sequence
@dataclass
class SubjectAltNames:
"""SEQUENCE OF IA5String — a list of DNS names."""
names: List[synta.IA5String] = field(default_factory=list)
@asn1_sequence
@dataclass
class TBSSketch:
"""Simplified TBS sketch with optional tagged fields."""
serial: synta.Integer
validity: Validity
# [0] EXPLICIT version — present only when not None
version: Optional[synta.Integer] = asn1_field(tag=0, explicit=True)
# [2] IMPLICIT key identifier — present only when not None
key_id: Optional[synta.OctetString] = asn1_field(tag=2, implicit=True)
# ---------------------------------------------------------------------------
# Demo functions
# ---------------------------------------------------------------------------
def demo_choice_basic():
section("1. CHOICE type — UTCTime variant")
t = Time(utc_time=synta.UtcTime(2024, 6, 1, 12, 0, 0))
der = t.to_der()
print(f" DER (UTCTime variant): {der.hex()}")
t2 = Time.from_der(der)
assert t2.utc_time is not None
assert t2.generalized_time is None
assert t2.utc_time.year == 2024
print(f" Decoded year: {t2.utc_time.year} (expected 2024)")
print(" UTCTime variant round-trip: OK")
def demo_choice_generalized():
section("2. CHOICE type — GeneralizedTime variant")
t = Time(generalized_time=synta.GeneralizedTime(2099, 12, 31, 23, 59, 59))
der = t.to_der()
print(f" DER (GeneralizedTime variant): {der.hex()}")
t2 = Time.from_der(der)
assert t2.utc_time is None
assert t2.generalized_time is not None
assert t2.generalized_time.year == 2099
print(f" Decoded year: {t2.generalized_time.year} (expected 2099)")
print(" GeneralizedTime variant round-trip: OK")
def demo_nested_sequence():
section("3. SEQUENCE with nested CHOICE fields")
v = Validity(
not_before=Time(utc_time=synta.UtcTime(2024, 1, 1, 0, 0, 0)),
not_after=Time(utc_time=synta.UtcTime(2025, 1, 1, 0, 0, 0)),
)
print(f" repr: {v!r}")
der = v.to_der()
print(f" DER: {der.hex()}")
v2 = Validity.from_der(der)
assert v == v2 # @dataclass __eq__
assert v2.not_before.utc_time.year == 2024
assert v2.not_after.utc_time.year == 2025
print(" Nested SEQUENCE round-trip: OK")
print(f" v == v2: {v == v2} (dataclass __eq__)")
def demo_optional_tagged_fields():
section("4. Optional and tagged fields")
# Both optional fields absent
tbs = TBSSketch(
serial=synta.Integer(1),
validity=Validity(
not_before=Time(utc_time=synta.UtcTime(2024, 1, 1, 0, 0, 0)),
not_after=Time(utc_time=synta.UtcTime(2025, 1, 1, 0, 0, 0)),
),
)
der_without = tbs.to_der()
tbs2 = TBSSketch.from_der(der_without)
assert tbs2.version is None
assert tbs2.key_id is None
print(" Both optional fields absent — decoded as None: OK")
# With [0] EXPLICIT version and [2] IMPLICIT key_id
tbs_full = TBSSketch(
serial=synta.Integer(42),
validity=Validity(
not_before=Time(utc_time=synta.UtcTime(2024, 3, 1, 0, 0, 0)),
not_after=Time(utc_time=synta.UtcTime(2025, 3, 1, 0, 0, 0)),
),
version=synta.Integer(2),
key_id=synta.OctetString(b"\xde\xad\xbe\xef"),
)
der_full = tbs_full.to_der()
tbs3 = TBSSketch.from_der(der_full)
assert tbs3.version.to_int() == 2
assert tbs3.key_id.to_bytes() == b"\xde\xad\xbe\xef"
print(f" [0] EXPLICIT version: {tbs3.version.to_int()} (expected 2)")
print(f" [2] IMPLICIT key_id: {tbs3.key_id.to_bytes().hex()} (expected deadbeef)")
print(" Optional tagged fields round-trip: OK")
def demo_sequence_of():
section("5. SEQUENCE OF (List field)")
san = SubjectAltNames(names=[
synta.IA5String("example.com"),
synta.IA5String("www.example.com"),
])
der = san.to_der()
print(f" DER: {der.hex()}")
san2 = SubjectAltNames.from_der(der)
assert len(san2.names) == 2
assert san2.names[0].as_str() == "example.com"
assert san2.names[1].as_str() == "www.example.com"
print(f" names[0]: {san2.names[0].as_str()!r}")
print(f" names[1]: {san2.names[1].as_str()!r}")
print(" SEQUENCE OF round-trip: OK")
# Empty list
empty = SubjectAltNames()
empty2 = SubjectAltNames.from_der(empty.to_der())
assert empty2.names == []
print(" Empty SEQUENCE OF round-trip: OK")
def demo_dataclass_features():
section("6. Dataclass features are preserved")
v = Validity(
not_before=Time(utc_time=synta.UtcTime(2024, 1, 1, 0, 0, 0)),
not_after=Time(utc_time=synta.UtcTime(2025, 1, 1, 0, 0, 0)),
)
# __repr__
r = repr(v)
assert "Validity" in r
print(f" repr: {r[:60]}...")
# __eq__
v2 = Validity.from_der(v.to_der())
assert v == v2
print(f" v == v2 (after round-trip): True")
# dataclasses.fields()
field_names = [f.name for f in dataclasses.fields(v)]
assert field_names == ["not_before", "not_after"]
print(f" dataclasses.fields: {field_names}")
def demo_error_all_none_choice():
section("7. ValueError — CHOICE with all fields None")
t = Time() # all fields default to None
try:
t.to_der()
print(" No error (unexpected)")
except ValueError as e:
print(f" ValueError: {e}")
def demo_error_required_none():
section("8. ValueError — required SEQUENCE field is None")
# Construct with serial=None bypassing dataclass type checking
tbs = TBSSketch.__new__(TBSSketch)
object.__setattr__(tbs, "serial", None)
object.__setattr__(tbs, "validity", Validity(
not_before=Time(utc_time=synta.UtcTime(2024, 1, 1, 0, 0, 0)),
not_after=Time(utc_time=synta.UtcTime(2025, 1, 1, 0, 0, 0)),
))
object.__setattr__(tbs, "version", None)
object.__setattr__(tbs, "key_id", None)
try:
tbs.to_der()
print(" No error (unexpected)")
except ValueError as e:
print(f" ValueError: {e}")
def demo_error_wrong_decorator_order():
section("9. TypeError — @asn1_sequence applied before @dataclass")
try:
@dataclass
@asn1_sequence # applied first — wrong order
class Bad:
x: synta.Integer
print(" No error (unexpected)")
except TypeError as e:
print(f" TypeError: {e}")
def main():
print("=" * 60)
print("Example 34: synta.schema — ASN.1 structure definitions")
print("=" * 60)
demo_choice_basic()
demo_choice_generalized()
demo_nested_sequence()
demo_optional_tagged_fields()
demo_sequence_of()
demo_dataclass_features()
demo_error_all_none_choice()
demo_error_required_none()
demo_error_wrong_decorator_order()
print("\nAll synta.schema examples completed.")
if __name__ == "__main__":
main()
```