Encode subclasses of the specially handled types instead of rejecting them - #329
binggao1230 wants to merge 3 commits into
Conversation
… 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.
c1bfade to
bbe2a25
Compare
agronholm
left a comment
There was a problem hiding this comment.
Overall looks good. I could add this in v7.0.
| py.import("re")?.getattr("Pattern")?.cast_into()?.unbind(), | ||
| CBOREncoder::encode_regexp, | ||
| ), | ||
| ( |
There was a problem hiding this comment.
Why did you move these around?
There was a problem hiding this comment.
Because the fallback takes the first isinstance() match. IPv4Interface and IPv6Interface subclass their address types, so the old order would encode interface subclasses as bare addresses and lose the prefix length.
| 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 |
There was a problem hiding this comment.
Agreed. I reduced this to two cases: datetime covers datetime/date precedence, and IPv4Interface covers interface/address precedence.
|
@binggao1230 do you intend to follow through with the changes I requested? |
|
Yes—sorry for the delay. I pushed 8e70a52 with the formatting and reduced tests, and explained the required table ordering in the review thread. |
Changes
Since the Rust rewrite, encoding a subclass of any of the specially handled types raises
CBOREncodeError:cbor2 5.x encoded these like their base type (verified on 5.9.0, both the C extension and the pure-Python implementation —
_find_encoderusedissubclass()), so things likepandas.TimestamporDecimal/UUIDsubclasses 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/IPv6Interfacebefore the address types, likedatetimebeforedate) — 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):
tests/) which would fail without your patchdocs/), in case of behavior changes or newfeatures
docs/versionhistory.rst).