Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The format follows the spirit of Keep a Changelog, and this project intends to u

- Added repository workflow defaults for future GitHub workflow sessions.
- Updated post-release documentation now that `0.3.0` is published.
- Hardened model factory handling for missing or null optional nested PrintNode
response fields.

## 0.3.0 - 2026-04-26

Expand Down
17 changes: 11 additions & 6 deletions printnode_community/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def create_computers(self, computers_dict):

def create_computer(self, computer_dict):
fields = self._underscore_keys(computer_dict)
del fields['jre']
fields.pop('jre', None)
self._rename_field(fields, 'inet_6', 'inet6')
return safe_tuple_populate(Computer, fields)

Expand All @@ -71,10 +71,9 @@ def create_printers(self, printers_dict):

def create_printer(self, printer_dict):
fields = self._underscore_keys(printer_dict)
fields['computer'] = self.create_computer(fields['computer'])
if fields['capabilities']:
fields['capabilities'] = self.create_capabilities(
fields['capabilities'])
self._create_nested_model(fields, 'computer', self.create_computer)
self._create_nested_model(
fields, 'capabilities', self.create_capabilities)
return safe_tuple_populate(Printer, fields)

def create_capabilities(self, capabilities_dict):
Expand All @@ -86,7 +85,7 @@ def create_printjobs(self, printjobs_dict):

def create_printjob(self, printjob_dict):
fields = self._underscore_keys(printjob_dict)
fields['printer'] = self.create_printer(fields['printer'])
self._create_nested_model(fields, 'printer', self.create_printer)
fields.setdefault('content', None)
fields.setdefault('pages', None)
fields.setdefault('qty', 1)
Expand All @@ -109,10 +108,16 @@ def _underscore_keys(self, input_dict):
for k, v in input_dict.items()}

def _rename_field(self, obj_dict, old_name, new_name):
if old_name not in obj_dict:
return
assert new_name not in obj_dict
obj_dict[new_name] = obj_dict[old_name]
del obj_dict[old_name]

def _create_nested_model(self, fields, field_name, factory):
if field_name in fields and fields[field_name] is not None:
fields[field_name] = factory(fields[field_name])

def _map(self, f, iter):
return list(map(f, iter))

Expand Down
41 changes: 41 additions & 0 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ def test_create_computer_maps_camel_case_and_ignores_jre():
assert not hasattr(computer, 'jre')


def test_create_computer_defaults_missing_optional_network_fields():
payload = computer_payload()
del payload['jre']
del payload['inet6']

computer = ModelFactory().create_computer(payload)

assert isinstance(computer, Computer)
assert computer.inet6 is None


def test_create_printer_builds_nested_computer_and_capabilities():
printer = ModelFactory().create_printer(printer_payload())

Expand All @@ -138,6 +149,27 @@ def test_create_printer_builds_nested_computer_and_capabilities():
assert printer.capabilities.supports_custom_paper_size is True


def test_create_printer_defaults_missing_optional_nested_fields():
payload = printer_payload()
del payload['computer']
del payload['capabilities']

printer = ModelFactory().create_printer(payload)

assert printer.computer is None
assert printer.capabilities is None


def test_create_printer_preserves_null_nested_fields():
printer = ModelFactory().create_printer(printer_payload(
computer=None,
capabilities=None,
))

assert printer.computer is None
assert printer.capabilities is None


def test_create_printjob_defaults_optional_fields():
printjob = ModelFactory().create_printjob(printjob_payload())

Expand All @@ -148,6 +180,15 @@ def test_create_printjob_defaults_optional_fields():
assert printjob.options == {}


def test_create_printjob_defaults_missing_printer():
payload = printjob_payload()
del payload['printer']

printjob = ModelFactory().create_printjob(payload)

assert printjob.printer is None


def test_create_printjob_preserves_server_supplied_optional_fields():
printjob = ModelFactory().create_printjob(printjob_payload(
content='https://example.com/file.pdf',
Expand Down