From 52286795a16117a3a378af6f7c90543b8d31e79d Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Fri, 29 May 2026 15:47:13 +0100 Subject: [PATCH 1/3] Add PyScript examples for pyasn1 Generated by apply_llm_response.py from prompts/pyasn1/response.toml. Examples included: - encode_decode_record: Encoding and decoding an ASN.1 record - ber_cer_der_codecs: BER, CER, and DER: three codecs, one value - native_python_bridge: Bridging pyasn1 and native Python Generated-By: apply_llm_response.py --- examples/pyasn1/README.md | 18 ++++ examples/pyasn1/ber_cer_der_codecs/code.py | 70 ++++++++++++++++ .../pyasn1/ber_cer_der_codecs/config.toml | 1 + examples/pyasn1/ber_cer_der_codecs/setup.py | 32 ++++++++ examples/pyasn1/encode_decode_record/code.py | 74 +++++++++++++++++ .../pyasn1/encode_decode_record/config.toml | 1 + examples/pyasn1/encode_decode_record/setup.py | 51 ++++++++++++ examples/pyasn1/native_python_bridge/code.py | 82 +++++++++++++++++++ .../pyasn1/native_python_bridge/config.toml | 1 + examples/pyasn1/native_python_bridge/setup.py | 30 +++++++ examples/pyasn1/order.json | 5 ++ 11 files changed, 365 insertions(+) create mode 100644 examples/pyasn1/README.md create mode 100644 examples/pyasn1/ber_cer_der_codecs/code.py create mode 100644 examples/pyasn1/ber_cer_der_codecs/config.toml create mode 100644 examples/pyasn1/ber_cer_der_codecs/setup.py create mode 100644 examples/pyasn1/encode_decode_record/code.py create mode 100644 examples/pyasn1/encode_decode_record/config.toml create mode 100644 examples/pyasn1/encode_decode_record/setup.py create mode 100644 examples/pyasn1/native_python_bridge/code.py create mode 100644 examples/pyasn1/native_python_bridge/config.toml create mode 100644 examples/pyasn1/native_python_bridge/setup.py create mode 100644 examples/pyasn1/order.json diff --git a/examples/pyasn1/README.md b/examples/pyasn1/README.md new file mode 100644 index 0000000..691e946 --- /dev/null +++ b/examples/pyasn1/README.md @@ -0,0 +1,18 @@ +# pyasn1 Examples + +Each sub-directory contains a self-contained example. The order in +which the examples are to appear is specified in `order.json` (an +array of directory names in the expected order). + +In each example directory you'll find: + +* `config.toml` - must conform to the specification outlined here: + https://docs.pyscript.net/latest/user-guide/configuration/ This is + parsed and ultimately turned into a JSON representation as part of + the package's API object. +* `setup.py` - Python code for contextual and environmental setup, + NOT SEEN BY THE END USER, but is run before the `code.py` code is + evaluated. Allows us to create useful (IPython) shims, avoid + repeating boilerplate and whatnot. +* `code.py` - the actual code added to the editor which forms the + practical example of using the package. diff --git a/examples/pyasn1/ber_cer_der_codecs/code.py b/examples/pyasn1/ber_cer_der_codecs/code.py new file mode 100644 index 0000000..0ab8485 --- /dev/null +++ b/examples/pyasn1/ber_cer_der_codecs/code.py @@ -0,0 +1,70 @@ +# pyasn1 decouples ASN.1 types from serialization. The same value can +# be encoded with several codecs: BER (basic), CER (canonical, useful +# for streaming), and DER (canonical, used in X.509, PKCS, etc.). + +# A SEQUENCE describing a tagged measurement: a sensor OID, a label, +# and an integer reading. +class Measurement(Sequence): + componentType = NamedTypes( + NamedType("sensor", ObjectIdentifier()), + NamedType("label", UTF8String()), + NamedType("reading", Integer()), + ) + + +reading = Measurement() +reading["sensor"] = "1.3.6.1.4.1.99999.1" # made-up enterprise OID +reading["label"] = "kitchen-thermometer" +reading["reading"] = 21 + +heading("One value, three encodings") +note( + "Each codec produces standards-compliant bytes. BER is the most " + "permissive; CER and DER are canonical (a given value has exactly " + "one valid encoding)." +) + +for name, encode in [("BER", ber_encode), ("CER", cer_encode), ("DER", der_encode)]: + substrate = encode(reading) + note(f"{name} ({len(substrate)} bytes):") + display(HTML(f"
{hexdump(substrate)}
"), append=True) + +heading("A SEQUENCE OF Measurement") +note( + "ASN.1 SEQUENCE OF is the natural fit for a homogeneous list. " + "We build three readings, encode the lot to DER, and round-trip " + "back to inspect the contents." +) + + +class MeasurementLog(SequenceOf): + componentType = Measurement() + + +log = MeasurementLog() +samples = [ + ("1.3.6.1.4.1.99999.1", "kitchen-thermometer", 21), + ("1.3.6.1.4.1.99999.2", "garage-thermometer", 8), + ("1.3.6.1.4.1.99999.3", "attic-thermometer", 27), +] +for oid, label, value in samples: + item = Measurement() + item["sensor"] = oid + item["label"] = label + item["reading"] = value + log.append(item) + +substrate = der_encode(log) +note(f"DER-encoded log is {len(substrate)} bytes.") +display(HTML(f"
{hexdump(substrate)}
"), append=True) + +decoded_log, _ = der_decode(substrate, asn1Spec=MeasurementLog()) +rows = [""] +for item in decoded_log: + rows.append( + f"" + f"" + f"" + ) +rows.append("
sensor OIDlabelreading
{item['sensor']}{item['label']}{int(item['reading'])}
") +display(HTML("".join(rows)), append=True) diff --git a/examples/pyasn1/ber_cer_der_codecs/config.toml b/examples/pyasn1/ber_cer_der_codecs/config.toml new file mode 100644 index 0000000..3b0ab7c --- /dev/null +++ b/examples/pyasn1/ber_cer_der_codecs/config.toml @@ -0,0 +1 @@ +packages = ["pyasn1"] diff --git a/examples/pyasn1/ber_cer_der_codecs/setup.py b/examples/pyasn1/ber_cer_der_codecs/setup.py new file mode 100644 index 0000000..ed8f157 --- /dev/null +++ b/examples/pyasn1/ber_cer_der_codecs/setup.py @@ -0,0 +1,32 @@ +"""Lighter setup: same names as cell 1, no IPython shim.""" +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display(*args, **kwargs, target=__pyscript_display_target__) + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +from pyasn1.type.univ import ( + Integer, OctetString, ObjectIdentifier, Sequence, SequenceOf, +) +from pyasn1.type.char import UTF8String +from pyasn1.type.namedtype import NamedTypes, NamedType +from pyasn1.codec.ber.encoder import encode as ber_encode +from pyasn1.codec.cer.encoder import encode as cer_encode +from pyasn1.codec.der.encoder import encode as der_encode +from pyasn1.codec.der.decoder import decode as der_decode + + +def hexdump(data): + return " ".join(f"{b:02X}" for b in data) diff --git a/examples/pyasn1/encode_decode_record/code.py b/examples/pyasn1/encode_decode_record/code.py new file mode 100644 index 0000000..a43629a --- /dev/null +++ b/examples/pyasn1/encode_decode_record/code.py @@ -0,0 +1,74 @@ +""" +A first look at pyasn1: defining an ASN.1 SEQUENCE, populating it, +encoding it to DER bytes, and decoding it back into a Python object. + +The ASN.1 schema we model here is the classic introductory example: + + Record ::= SEQUENCE { + id INTEGER, + room [0] INTEGER OPTIONAL, + house [1] INTEGER DEFAULT 0 + } + +Docs: https://pyasn1.readthedocs.io/ +""" +from IPython.core.display import display, HTML + + +# Define the Record schema as a Python class. Each named type maps to a +# field in the SEQUENCE; the context-specific implicit tags ([0], [1]) +# are attached via .subtype(implicitTag=...). +class Record(Sequence): + componentType = NamedTypes( + NamedType("id", Integer()), + OptionalNamedType( + "room", + Integer().subtype( + implicitTag=Tag(tagClassContext, tagFormatSimple, 0) + ), + ), + DefaultedNamedType( + "house", + Integer(0).subtype( + implicitTag=Tag(tagClassContext, tagFormatSimple, 1) + ), + ), + ) + + +heading("Building a Record value") +note( + "We populate the SEQUENCE much like a dict, then ask pyasn1 for " + "its human-readable form via str()." +) + +record = Record() +record["id"] = 123 +record["room"] = 321 + +display(HTML(f"
{str(record)}
"), append=True) + +heading("Encoding to DER") +note( + "DER (Distinguished Encoding Rules) gives us a compact, canonical " + "byte representation. Notice how the optional 'room' field is " + "included but the defaulted 'house' field is omitted." +) + +substrate = der_encode(record) +display(HTML(f"
DER bytes: {hexdump(substrate)}
"), append=True) +display(HTML(f"
Length:    {len(substrate)} bytes
"), append=True) + +heading("Decoding DER back into a Record") +note( + "Pass the schema (asn1Spec=Record()) so the decoder knows how to " + "interpret the implicit tags. The defaulted 'house' field comes " + "back as 0 even though it was not present in the bytes." +) + +decoded, leftover = der_decode(substrate, asn1Spec=Record()) +for field_name in ("id", "room", "house"): + note(f"{field_name} = {int(decoded[field_name])}") + +note(f"Leftover bytes after decoding: {len(leftover)}") +note(f"Round-trip equal? {record == decoded}") diff --git a/examples/pyasn1/encode_decode_record/config.toml b/examples/pyasn1/encode_decode_record/config.toml new file mode 100644 index 0000000..3b0ab7c --- /dev/null +++ b/examples/pyasn1/encode_decode_record/config.toml @@ -0,0 +1 @@ +packages = ["pyasn1"] diff --git a/examples/pyasn1/encode_decode_record/setup.py b/examples/pyasn1/encode_decode_record/setup.py new file mode 100644 index 0000000..9fc6b52 --- /dev/null +++ b/examples/pyasn1/encode_decode_record/setup.py @@ -0,0 +1,51 @@ +"""Shim setup for the first example. Includes the full IPython shim.""" +import sys +import types +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display( + *args, **kwargs, target=__pyscript_display_target__, + ) + + +ipython = types.ModuleType("IPython") +core = types.ModuleType("IPython.core") +core_display = types.ModuleType("IPython.core.display") +core_display.display = display +core_display.HTML = HTML +ipython.core = core +core.display = core_display +ipython.get_ipython = lambda: None +ipython.display = core_display +sys.modules["IPython"] = ipython +sys.modules["IPython.core"] = core +sys.modules["IPython.core.display"] = core_display +sys.modules["IPython.display"] = core_display + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +# pyasn1 imports used by this example. +from pyasn1.type.univ import Integer, Sequence +from pyasn1.type.namedtype import ( + NamedTypes, NamedType, OptionalNamedType, DefaultedNamedType, +) +from pyasn1.type.tag import Tag, tagClassContext, tagFormatSimple +from pyasn1.codec.der.encoder import encode as der_encode +from pyasn1.codec.der.decoder import decode as der_decode + + +def hexdump(data): + """Return a space-separated hex string of the bytes in `data`.""" + return " ".join(f"{b:02X}" for b in data) diff --git a/examples/pyasn1/native_python_bridge/code.py b/examples/pyasn1/native_python_bridge/code.py new file mode 100644 index 0000000..ad1e2e8 --- /dev/null +++ b/examples/pyasn1/native_python_bridge/code.py @@ -0,0 +1,82 @@ +# The "native" codec converts pyasn1 objects to plain Python dicts/lists +# and back. This is handy for moving data between an ASN.1-shaped wire +# format and the dict/list world of JSON, configs, and tests. + +class Address(Sequence): + componentType = NamedTypes( + NamedType("street", UTF8String()), + NamedType("city", UTF8String()), + OptionalNamedType("postcode", UTF8String()), + ) + + +class Contact(Sequence): + componentType = NamedTypes( + NamedType("name", UTF8String()), + NamedType("age", Integer()), + NamedType("address", Address()), + ) + + +class ContactBook(SequenceOf): + componentType = Contact() + + +heading("From Python dicts into pyasn1") +note( + "Start with ordinary Python data, then hand it to the native " + "decoder along with a schema. pyasn1 builds the corresponding " + "ASN.1 object tree." +) + +people = [ + { + "name": "Ada Lovelace", + "age": 36, + "address": {"street": "1 Analytical Way", "city": "London"}, + }, + { + "name": "Grace Hopper", + "age": 85, + "address": { + "street": "42 Compiler Ave", + "city": "Arlington", + "postcode": "22202", + }, + }, +] + +book = from_python(people, asn1Spec=ContactBook()) +display(HTML(f"
{str(book)}
"), append=True) + +heading("Round-tripping through DER") +note( + "Encode to DER for the wire, decode back, then flatten to native " + "Python with the native encoder. Optional fields that were absent " + "stay absent in the output." +) + +wire = der_encode(book) +note(f"DER payload is {len(wire)} bytes.") +display(HTML(f"
{hexdump(wire)}
"), append=True) + +restored, _ = der_decode(wire, asn1Spec=ContactBook()) +as_python = to_python(restored) + +# Render the recovered Python structure as an HTML table. +rows = [""] +for entry in as_python: + addr = entry["address"] + rows.append( + f"" + f"" + f"" + f"" + ) +rows.append("
nameagecitypostcode
{entry['name']}{entry['age']}{addr['city']}{addr.get('postcode', '—')}
") +display(HTML("".join(rows)), append=True) + +note( + "Tip: pair this with pyasn1-modules to work with " + "real-world ASN.1 schemas like X.509 certificates, PKCS, and SNMP." +) diff --git a/examples/pyasn1/native_python_bridge/config.toml b/examples/pyasn1/native_python_bridge/config.toml new file mode 100644 index 0000000..3b0ab7c --- /dev/null +++ b/examples/pyasn1/native_python_bridge/config.toml @@ -0,0 +1 @@ +packages = ["pyasn1"] diff --git a/examples/pyasn1/native_python_bridge/setup.py b/examples/pyasn1/native_python_bridge/setup.py new file mode 100644 index 0000000..d12fecc --- /dev/null +++ b/examples/pyasn1/native_python_bridge/setup.py @@ -0,0 +1,30 @@ +"""Lighter setup: same names as cell 1, no IPython shim.""" +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display(*args, **kwargs, target=__pyscript_display_target__) + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +from pyasn1.type.univ import Integer, Sequence, SequenceOf +from pyasn1.type.char import UTF8String +from pyasn1.type.namedtype import NamedTypes, NamedType, OptionalNamedType +from pyasn1.codec.der.encoder import encode as der_encode +from pyasn1.codec.der.decoder import decode as der_decode +from pyasn1.codec.native.encoder import encode as to_python +from pyasn1.codec.native.decoder import decode as from_python + + +def hexdump(data): + return " ".join(f"{b:02X}" for b in data) diff --git a/examples/pyasn1/order.json b/examples/pyasn1/order.json new file mode 100644 index 0000000..8bbc9a3 --- /dev/null +++ b/examples/pyasn1/order.json @@ -0,0 +1,5 @@ +[ + "encode_decode_record", + "ber_cer_der_codecs", + "native_python_bridge" +] From 06f5f602eb5afe88c885d668d33bd643d950129b Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Thu, 11 Jun 2026 12:53:57 +0100 Subject: [PATCH 2/3] Fix imports. --- examples/pyasn1/ber_cer_der_codecs/code.py | 14 ++++++++++++++ examples/pyasn1/ber_cer_der_codecs/setup.py | 14 -------------- examples/pyasn1/encode_decode_record/code.py | 14 ++++++++++++++ examples/pyasn1/encode_decode_record/setup.py | 14 -------------- examples/pyasn1/native_python_bridge/code.py | 13 +++++++++++++ examples/pyasn1/native_python_bridge/setup.py | 13 ------------- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/examples/pyasn1/ber_cer_der_codecs/code.py b/examples/pyasn1/ber_cer_der_codecs/code.py index 0ab8485..b098fbe 100644 --- a/examples/pyasn1/ber_cer_der_codecs/code.py +++ b/examples/pyasn1/ber_cer_der_codecs/code.py @@ -1,6 +1,20 @@ # pyasn1 decouples ASN.1 types from serialization. The same value can # be encoded with several codecs: BER (basic), CER (canonical, useful # for streaming), and DER (canonical, used in X.509, PKCS, etc.). +from pyasn1.type.univ import ( + Integer, OctetString, ObjectIdentifier, Sequence, SequenceOf, +) +from pyasn1.type.char import UTF8String +from pyasn1.type.namedtype import NamedTypes, NamedType +from pyasn1.codec.ber.encoder import encode as ber_encode +from pyasn1.codec.cer.encoder import encode as cer_encode +from pyasn1.codec.der.encoder import encode as der_encode +from pyasn1.codec.der.decoder import decode as der_decode + + +def hexdump(data): + return " ".join(f"{b:02X}" for b in data) + # A SEQUENCE describing a tagged measurement: a sensor OID, a label, # and an integer reading. diff --git a/examples/pyasn1/ber_cer_der_codecs/setup.py b/examples/pyasn1/ber_cer_der_codecs/setup.py index ed8f157..c8b7b45 100644 --- a/examples/pyasn1/ber_cer_der_codecs/setup.py +++ b/examples/pyasn1/ber_cer_der_codecs/setup.py @@ -16,17 +16,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - -from pyasn1.type.univ import ( - Integer, OctetString, ObjectIdentifier, Sequence, SequenceOf, -) -from pyasn1.type.char import UTF8String -from pyasn1.type.namedtype import NamedTypes, NamedType -from pyasn1.codec.ber.encoder import encode as ber_encode -from pyasn1.codec.cer.encoder import encode as cer_encode -from pyasn1.codec.der.encoder import encode as der_encode -from pyasn1.codec.der.decoder import decode as der_decode - - -def hexdump(data): - return " ".join(f"{b:02X}" for b in data) diff --git a/examples/pyasn1/encode_decode_record/code.py b/examples/pyasn1/encode_decode_record/code.py index a43629a..5fd9de2 100644 --- a/examples/pyasn1/encode_decode_record/code.py +++ b/examples/pyasn1/encode_decode_record/code.py @@ -13,6 +13,20 @@ Docs: https://pyasn1.readthedocs.io/ """ from IPython.core.display import display, HTML +# pyasn1 imports used by this example. +from pyasn1.type.univ import Integer, Sequence +from pyasn1.type.namedtype import ( + NamedTypes, NamedType, OptionalNamedType, DefaultedNamedType, +) +from pyasn1.type.tag import Tag, tagClassContext, tagFormatSimple +from pyasn1.codec.der.encoder import encode as der_encode +from pyasn1.codec.der.decoder import decode as der_decode + + +def hexdump(data): + """Return a space-separated hex string of the bytes in `data`.""" + return " ".join(f"{b:02X}" for b in data) + # Define the Record schema as a Python class. Each named type maps to a diff --git a/examples/pyasn1/encode_decode_record/setup.py b/examples/pyasn1/encode_decode_record/setup.py index 9fc6b52..17b1db0 100644 --- a/examples/pyasn1/encode_decode_record/setup.py +++ b/examples/pyasn1/encode_decode_record/setup.py @@ -35,17 +35,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - -# pyasn1 imports used by this example. -from pyasn1.type.univ import Integer, Sequence -from pyasn1.type.namedtype import ( - NamedTypes, NamedType, OptionalNamedType, DefaultedNamedType, -) -from pyasn1.type.tag import Tag, tagClassContext, tagFormatSimple -from pyasn1.codec.der.encoder import encode as der_encode -from pyasn1.codec.der.decoder import decode as der_decode - - -def hexdump(data): - """Return a space-separated hex string of the bytes in `data`.""" - return " ".join(f"{b:02X}" for b in data) diff --git a/examples/pyasn1/native_python_bridge/code.py b/examples/pyasn1/native_python_bridge/code.py index ad1e2e8..71840bc 100644 --- a/examples/pyasn1/native_python_bridge/code.py +++ b/examples/pyasn1/native_python_bridge/code.py @@ -2,6 +2,19 @@ # and back. This is handy for moving data between an ASN.1-shaped wire # format and the dict/list world of JSON, configs, and tests. +from pyasn1.type.univ import Integer, Sequence, SequenceOf +from pyasn1.type.char import UTF8String +from pyasn1.type.namedtype import NamedTypes, NamedType, OptionalNamedType +from pyasn1.codec.der.encoder import encode as der_encode +from pyasn1.codec.der.decoder import decode as der_decode +from pyasn1.codec.native.encoder import encode as to_python +from pyasn1.codec.native.decoder import decode as from_python + + +def hexdump(data): + return " ".join(f"{b:02X}" for b in data) + + class Address(Sequence): componentType = NamedTypes( NamedType("street", UTF8String()), diff --git a/examples/pyasn1/native_python_bridge/setup.py b/examples/pyasn1/native_python_bridge/setup.py index d12fecc..1646442 100644 --- a/examples/pyasn1/native_python_bridge/setup.py +++ b/examples/pyasn1/native_python_bridge/setup.py @@ -15,16 +15,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - - -from pyasn1.type.univ import Integer, Sequence, SequenceOf -from pyasn1.type.char import UTF8String -from pyasn1.type.namedtype import NamedTypes, NamedType, OptionalNamedType -from pyasn1.codec.der.encoder import encode as der_encode -from pyasn1.codec.der.decoder import decode as der_decode -from pyasn1.codec.native.encoder import encode as to_python -from pyasn1.codec.native.decoder import decode as from_python - - -def hexdump(data): - return " ".join(f"{b:02X}" for b in data) From ead6d1f7eb0b6743b0297011bd29233d1a2ed337 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Fri, 26 Jun 2026 10:50:05 +0100 Subject: [PATCH 3/3] Add version_info to IPython shim for PyScript update. --- examples/pyasn1/encode_decode_record/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/pyasn1/encode_decode_record/setup.py b/examples/pyasn1/encode_decode_record/setup.py index 17b1db0..419dff5 100644 --- a/examples/pyasn1/encode_decode_record/setup.py +++ b/examples/pyasn1/encode_decode_record/setup.py @@ -20,6 +20,7 @@ def display(*args, **kwargs): core_display.HTML = HTML ipython.core = core core.display = core_display +ipython.version_info = (9, 0, 2, '') ipython.get_ipython = lambda: None ipython.display = core_display sys.modules["IPython"] = ipython