Skip to content

Commit

Permalink
OPT_PASSTHROUGH_DATETIME
Browse files Browse the repository at this point in the history
  • Loading branch information
ijl committed Jun 8, 2020
1 parent c6f604f commit 54bac8f
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 5 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,34 @@ b'"1970-01-01T00:00:00.000001"'
b'"1970-01-01T00:00:00"'
```

##### OPT_PASSTHROUGH_DATETIME

Passthrough `datetime.datetime`, `datetime.date`, and `datetime.time` instances
to `default`. This allows serializing datetimes to a custom format, e.g.,
HTTP dates:

```python
>>> import orjson, datetime
>>>
def default(obj):
if isinstance(obj, datetime.datetime):
return obj.strftime("%a, %d %b %Y %H:%M:%S GMT")
raise TypeError

>>> orjson.dumps({"created_at": datetime.datetime(1970, 1, 1)})
b'{"created_at":"1970-01-01T00:00:00"}'
>>> orjson.dumps({"created_at": datetime.datetime(1970, 1, 1)}, option=orjson.OPT_PASSTHROUGH_DATETIME)
TypeError: Type is not JSON serializable: datetime.datetime
>>> orjson.dumps(
{"created_at": datetime.datetime(1970, 1, 1)},
option=orjson.OPT_PASSTHROUGH_DATETIME,
default=default,
)
b'{"created_at":"Thu, 01 Jan 1970 00:00:00 GMT"}'
```

This does not affect datetimes in `dict` keys if using OPT_NON_STR_KEYS.

##### OPT_PASSTHROUGH_SUBCLASS

Passthrough subclasses of builtin types to `default`.
Expand Down Expand Up @@ -624,6 +652,9 @@ It is faster to have orjson serialize datetime objects than to do so
before calling `dumps()`. If using an unsupported type such as
`pendulum.datetime`, use `default`.

To disable serialization of `datetime` objects specify the option
`orjson.OPT_PASSTHROUGH_DATETIME`.

### enum

orjson serializes enums natively. Options apply to their values.
Expand Down
1 change: 1 addition & 0 deletions orjson.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ OPT_INDENT_2: int
OPT_NAIVE_UTC: int
OPT_NON_STR_KEYS: int
OPT_OMIT_MICROSECONDS: int
OPT_PASSTHROUGH_DATETIME: int
OPT_PASSTHROUGH_SUBCLASS: int
OPT_SERIALIZE_DATACLASS: int
OPT_SERIALIZE_NUMPY: int
Expand Down
6 changes: 3 additions & 3 deletions src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub fn pyobject_to_obtype(obj: *mut pyo3::ffi::PyObject, opts: Opt) -> ObType {
ObType::List
} else if ob_type == DICT_TYPE {
ObType::Dict
} else if ob_type == DATETIME_TYPE {
} else if ob_type == DATETIME_TYPE && opts & PASSTHROUGH_DATETIME == 0 {
ObType::Datetime
} else {
pyobject_to_obtype_unlikely(obj, opts)
Expand All @@ -110,9 +110,9 @@ macro_rules! is_subclass {
pub fn pyobject_to_obtype_unlikely(obj: *mut pyo3::ffi::PyObject, opts: Opt) -> ObType {
unsafe {
let ob_type = ob_type!(obj);
if ob_type == DATE_TYPE {
if ob_type == DATE_TYPE && opts & PASSTHROUGH_DATETIME == 0 {
ObType::Date
} else if ob_type == TIME_TYPE {
} else if ob_type == TIME_TYPE && opts & PASSTHROUGH_DATETIME == 0 {
ObType::Time
} else if ob_type == TUPLE_TYPE {
ObType::Tuple
Expand Down
5 changes: 5 additions & 0 deletions src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ pub unsafe extern "C" fn PyInit_orjson() -> *mut PyObject {
opt!(mptr, "OPT_NAIVE_UTC\0", opt::NAIVE_UTC);
opt!(mptr, "OPT_NON_STR_KEYS\0", opt::NON_STR_KEYS);
opt!(mptr, "OPT_OMIT_MICROSECONDS\0", opt::OMIT_MICROSECONDS);
opt!(
mptr,
"OPT_PASSTHROUGH_DATETIME\0",
opt::PASSTHROUGH_DATETIME
);
opt!(
mptr,
"OPT_PASSTHROUGH_SUBCLASS\0",
Expand Down
4 changes: 3 additions & 1 deletion src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@ pub const SORT_KEYS: Opt = 1 << 5;
pub const STRICT_INTEGER: Opt = 1 << 6;
pub const UTC_Z: Opt = 1 << 7;
pub const PASSTHROUGH_SUBCLASS: Opt = 1 << 8;
pub const PASSTHROUGH_DATETIME: Opt = 1 << 9;

// deprecated
pub const SERIALIZE_DATACLASS: Opt = 0;
pub const SERIALIZE_UUID: Opt = 0;

pub const SORT_OR_NON_STR_KEYS: Opt = SORT_KEYS | NON_STR_KEYS;

pub const NOT_PASSTHROUGH: Opt = !PASSTHROUGH_SUBCLASS;
pub const NOT_PASSTHROUGH: Opt = !(PASSTHROUGH_DATETIME | PASSTHROUGH_SUBCLASS);

pub const MAX_OPT: i32 = (INDENT_2
| NAIVE_UTC
| NON_STR_KEYS
| OMIT_MICROSECONDS
| PASSTHROUGH_DATETIME
| PASSTHROUGH_SUBCLASS
| SERIALIZE_DATACLASS
| SERIALIZE_NUMPY
Expand Down
2 changes: 1 addition & 1 deletion test/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def test_option_range_high(self):
dumps() option out of range high
"""
with self.assertRaises(orjson.JSONEncodeError):
orjson.dumps(True, option=1 << 9)
orjson.dumps(True, option=1 << 10)

def test_opts_multiple(self):
"""
Expand Down
33 changes: 33 additions & 0 deletions test/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,3 +566,36 @@ def test_time_microsecond_min(self):
datetime.time microsecond min
"""
self.assertEqual(orjson.dumps(datetime.time(0, 0, 0, 1)), b'"00:00:00.000001"')


class DateclassPassthroughTests(unittest.TestCase):
def test_passthrough_datetime(self):
with self.assertRaises(orjson.JSONEncodeError):
orjson.dumps(
datetime.datetime(1970, 1, 1), option=orjson.OPT_PASSTHROUGH_DATETIME
)

def test_passthrough_date(self):
with self.assertRaises(orjson.JSONEncodeError):
orjson.dumps(
datetime.date(1970, 1, 1), option=orjson.OPT_PASSTHROUGH_DATETIME
)

def test_passthrough_time(self):
with self.assertRaises(orjson.JSONEncodeError):
orjson.dumps(
datetime.time(12, 0, 0), option=orjson.OPT_PASSTHROUGH_DATETIME
)

def test_passthrough_datetime_default(self):
def default(obj):
return obj.strftime("%a, %d %b %Y %H:%M:%S GMT")

self.assertEqual(
orjson.dumps(
datetime.datetime(1970, 1, 1),
option=orjson.OPT_PASSTHROUGH_DATETIME,
default=default,
),
b'"Thu, 01 Jan 1970 00:00:00 GMT"',
)
12 changes: 12 additions & 0 deletions test/test_non_str_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ def test_dict_keys_datetime_opt(self):
b'{"2000-01-01T02:03:04Z":true}',
)

def test_dict_keys_datetime_passthrough(self):
"""
OPT_PASSTHROUGH_DATETIME does not affect OPT_NON_STR_KEYS
"""
self.assertEqual(
orjson.dumps(
{datetime.datetime(2000, 1, 1, 2, 3, 4, 123): True},
option=orjson.OPT_NON_STR_KEYS | orjson.OPT_PASSTHROUGH_DATETIME,
),
b'{"2000-01-01T02:03:04.000123":true}',
)

def test_dict_keys_uuid(self):
"""
OPT_NON_STR_KEYS always serializes UUID as keys
Expand Down

0 comments on commit 54bac8f

Please sign in to comment.