Skip to content

Encode subclasses of the specially handled types instead of rejecting them - #329

Open
binggao1230 wants to merge 2 commits into
agronholm:7.0from
binggao1230:fix-stdlib-subclass-encoding
Open

Encode subclasses of the specially handled types instead of rejecting them#329
binggao1230 wants to merge 2 commits into
agronholm:7.0from
binggao1230:fix-stdlib-subclass-encoding

Conversation

@binggao1230

Copy link
Copy Markdown

Changes

Since the Rust rewrite, encoding a subclass of any of the specially handled types raises CBOREncodeError:

>>> import cbor2, datetime
>>> class MyDatetime(datetime.datetime): pass
>>> cbor2.dumps(MyDatetime(2020, 1, 1, tzinfo=datetime.timezone.utc))
cbor2.CBOREncodeError: cannot encode type <class '__main__.MyDatetime'>

cbor2 5.x encoded these like their base type (verified on 5.9.0, both the C extension and the pure-Python implementation — _find_encoder used issubclass()), so things like pandas.Timestamp or Decimal/UUID subclasses used to work. It's also inconsistent with the primitives, whose subclasses (int, str, bytes, ...) still encode fine in 6.x. The 6.0 changelog doesn't list this among the backward-incompatible changes, so I'm assuming it's unintended.

The fix keeps the exact-identity pass over the stdlib encoder table first, so the common case still only pays pointer comparisons, and adds an isinstance() fallback pass for subclasses. The table is reordered so subclasses precede their bases in that fallback (IPv4Interface/IPv6Interface before the address types, like datetime before date) — so an interface subclass is encoded as an interface, not silently downgraded to a bare address the way 5.x did it.

Checklist

If this is a user-facing code change, like a bugfix or a new feature, please ensure that
you've fulfilled the following conditions (where applicable):

  • You've added tests (in tests/) which would fail without your patch
  • You've updated the documentation (in docs/), in case of behavior changes or new
    features
  • You've added a new changelog entry (in docs/versionhistory.rst).

… them

The stdlib encoder table was matched by exact type identity, so subclasses
of datetime, Decimal, UUID, the ipaddress types etc. raised CBOREncodeError
where cbor2 5.x encoded them like their base type. Keep the exact-identity
pass first for the common case, then fall back to isinstance matching, with
the table ordered so subclasses precede their bases.
@binggao1230
binggao1230 force-pushed the fix-stdlib-subclass-encoding branch from c1bfade to bbe2a25 Compare July 15, 2026 20:29
@coveralls

coveralls commented Jul 15, 2026

Copy link
Copy Markdown

Coverage Status

Coverage is 94.822%gaoflow:fix-stdlib-subclass-encoding into agronholm:7.0. No base build found for agronholm:7.0.

@agronholm
agronholm changed the base branch from master to 7.0 August 19, 2026 17:54

@agronholm agronholm left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good. I could add this in v7.0.

Comment thread rust/encoder.rs
py.import("re")?.getattr("Pattern")?.cast_into()?.unbind(),
CBOREncoder::encode_regexp,
),
(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you move these around?

Comment thread rust/encoder.rs
),
])
})?;
// Exact type matches first: the common case, using cheap pointer comparisons

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Exact type matches first: the common case, using cheap pointer comparisons
// Exact type matches first: the common case, using cheap pointer comparisons

Comment thread tests/test_encoder.py
Comment on lines +439 to +484
class _DatetimeSubclass(datetime):
pass


class _DateSubclass(date):
pass


class _DecimalSubclass(Decimal):
pass


class _FractionSubclass(Fraction):
pass


class _UUIDSubclass(UUID):
pass


class _IPv4AddressSubclass(IPv4Address):
pass


class _IPv4NetworkSubclass(IPv4Network):
pass


class _IPv4InterfaceSubclass(IPv4Interface):
pass


class _IPv6AddressSubclass(IPv6Address):
pass


class _IPv6NetworkSubclass(IPv6Network):
pass


class _IPv6InterfaceSubclass(IPv6Interface):
pass


class _MIMETextSubclass(MIMEText):
pass

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems rather excessive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants