Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ While in pre-release they are subject to change.
| Any | `typing.Any` |
| Unions (A\|B\|C) | `typing.Union[A\|B\|C]` |
| Regex | `pkl.Regex` |
| Bytes | `bytes` |

## Contributing
Contributions are welcome! If you'd like to contribute, please fork the repository and submit a pull request. For major changes, please open an issue first to discuss what you would like to change.
Expand Down
2 changes: 1 addition & 1 deletion scripts/download_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import requests

VERSION = "0.25.2"
VERSION = "0.30.0"

BASE_PATH = "https://github.com/apple/pkl/releases/download/"
filenames = [
Expand Down
15 changes: 10 additions & 5 deletions src/pkl/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ResultType(Enum):
CODE_REGEX = 0xB
CODE_CLASS = 0xC
CODE_TYPEALIAS = 0xD
CODE_BYTES = 0xF
CODE_PROPERTY = 0x10
CODE_ENTRY = 0x11
CODE_ELEMENT = 0x12
Expand Down Expand Up @@ -114,6 +115,7 @@ def __init__(
CODE_REGEX: self.parse_regex,
CODE_CLASS: self.parse_class,
CODE_TYPEALIAS: self.parse_typealias,
CODE_BYTES: self.parse_bytes,
CODE_PROPERTY: self.parse_property,
CODE_ENTRY: self.parse_entry,
CODE_ELEMENT: self.parse_element,
Expand Down Expand Up @@ -155,7 +157,7 @@ def get_dataclass_class(self, class_name: str, keys: List[str], no_cache: bool =
return dynamic_class

def parse_typed_dynamic(self, obj):
_, full_class_name, module_uri, members = obj
full_class_name, module_uri, members = obj[1], obj[2], obj[3]

member_types = set(m[0] for m in members)
property_list = list(map(self.handle_type, members))
Expand Down Expand Up @@ -202,7 +204,7 @@ def parse_set(self, obj):
return set(obj[1])

def parse_duration(self, obj):
_, value, unit = obj
value, unit = obj[1], obj[2]

return Duration(value, unit)

Expand All @@ -224,14 +226,17 @@ def parse_class(self, obj):
def parse_typealias(self, obj):
return

def parse_bytes(self, obj):
return obj[1]

def parse_property(self, obj):
_, key, value = obj
key, value = obj[1], obj[2]
return {key: self.handle_type(value)}

def parse_entry(self, obj):
_, key, value = obj
key, value = obj[1], obj[2]
return {key: self.handle_type(value)}

def parse_element(self, obj):
_, index, value = obj
index, value = obj[1], obj[2]
return {index: self.handle_type(value)}
41 changes: 41 additions & 0 deletions tests/test_parser_binary_encoding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Unit tests for pkl.parser.Parser against the pkl-binary wire encoding.

These exercise the decoder directly against hand-built MessagePack-style
arrays (as documented in bindings-specification/binary-encoding.adoc), so
they don't need a real `pkl` binary/subprocess.
"""

from pkl.parser import CODE_BYTES, CODE_DURATION, Parser


def test_bytes_decodes_to_python_bytes():
"""`Bytes` (type code 0x0F) added in Pkl 0.29; wire encoding documented
as [0x0F, <bin>]. Previously unhandled, so the parser silently returned
{"type": "Unknown", "value": obj} instead of the actual bytes.
"""
parser = Parser()
obj = [CODE_BYTES, b"\x01\x02\x03"]

result = parser.handle_type(obj)

assert result == b"\x01\x02\x03"
assert isinstance(result, bytes)


def test_decoder_ignores_unknown_trailing_slots():
"""Pkl 0.30's binary-encoding.adoc formalizes forward compatibility:
"Additional slots may be added to types in future Pkl releases.
Decoders *must* be designed to defensively discard values beyond the
number of known slots for a type[...]".

Fixed-arity unpacking (`_, value, unit = obj`) would raise
"too many values to unpack" on a future Pkl release that appends a
slot; decoders must tolerate and ignore extras instead.
"""
parser = Parser()
obj = [CODE_DURATION, 5.0, "s", "some-future-slot"]

result = parser.handle_type(obj)

assert result.value == 5.0
assert result.unit == "s"