-
Notifications
You must be signed in to change notification settings - Fork 1.1k
PYTHON-5395 - Add convert_decimal to CodecOptions #2491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b24288e
1b78a33
9dc5b86
7a25761
4fcd939
02fa596
bda0041
8b6a1f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,6 +72,7 @@ | |
from __future__ import annotations | ||
|
||
import datetime | ||
import decimal | ||
import itertools | ||
import os | ||
import re | ||
|
@@ -858,6 +859,16 @@ def _encode_decimal128(name: bytes, value: Decimal128, dummy0: Any, dummy1: Any) | |
return b"\x13" + name + value.bid | ||
|
||
|
||
def _encode_python_decimal( | ||
name: bytes, value: decimal.Decimal, dummy0: Any, opts: CodecOptions[Any] | ||
) -> bytes: | ||
if opts.convert_decimal: | ||
converted = Decimal128(value) | ||
return b"\x13" + name + converted.bid | ||
else: | ||
raise InvalidDocument("decimal.Decimal must be converted to bson.decimal128.Decimal128.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest mentioning the convert_decimal option in this error message. |
||
|
||
|
||
def _encode_minkey(name: bytes, dummy0: Any, dummy1: Any, dummy2: Any) -> bytes: | ||
"""Encode bson.min_key.MinKey.""" | ||
return b"\xFF" + name | ||
|
@@ -937,6 +948,9 @@ def _name_value_to_bson( | |
# Give the fallback_encoder a chance | ||
was_integer_overflow = True | ||
|
||
if opts.convert_decimal and type(value) == decimal.Decimal: | ||
return _encode_python_decimal(name, value, check_keys, opts) | ||
|
||
# Second, fall back to trying _type_marker. This has to be done | ||
# before the loop below since users could subclass one of our | ||
# custom types that subclasses a python built-in (e.g. Binary) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -159,6 +159,27 @@ extern int cbson_long_long_to_str(long long num, char* str, size_t size) { | |
return 0; | ||
} | ||
|
||
int _check_decimal(PyObject *value) { | ||
static PyObject *decimal_module = NULL; | ||
static PyObject *decimal_class = NULL; | ||
|
||
if (decimal_module == NULL) { | ||
decimal_module = PyImport_ImportModule("decimal"); | ||
if (decimal_module == NULL) { | ||
PyErr_SetString(PyExc_ImportError, "Failed to import decimal module"); | ||
return -1; | ||
} | ||
decimal_class = PyObject_GetAttrString(decimal_module, "Decimal"); | ||
if (decimal_class == NULL) { | ||
Py_DECREF(decimal_module); | ||
decimal_module = NULL; | ||
PyErr_SetString(PyExc_AttributeError, "Failed to get Decimal class"); | ||
return -1; | ||
} | ||
} | ||
return PyObject_IsInstance(value, decimal_class); | ||
} | ||
|
||
static PyObject* _test_long_long_to_str(PyObject* self, PyObject* args) { | ||
// Test extreme values | ||
Py_ssize_t maxNum = PY_SSIZE_T_MAX; | ||
|
@@ -791,14 +812,15 @@ int convert_codec_options(PyObject* self, PyObject* options_obj, codec_options_t | |
|
||
options->unicode_decode_error_handler = NULL; | ||
|
||
if (!PyArg_ParseTuple(options_obj, "ObbzOOb", | ||
if (!PyArg_ParseTuple(options_obj, "ObbzOObb", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I stared at this for a while but then found: https://docs.python.org/3/c-api/arg.html. So, just confirming that's a format string for the args below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lol i was sitting on this one too, googled it, found that same site, was still confused / overwhelemed by words so then i asked mongo-gpt HAHA There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup this is a format string. |
||
&options->document_class, | ||
&options->tz_aware, | ||
&options->uuid_rep, | ||
&options->unicode_decode_error_handler, | ||
&options->tzinfo, | ||
&type_registry_obj, | ||
&options->datetime_conversion)) { | ||
&options->datetime_conversion, | ||
&options->convert_decimal)) { | ||
return 0; | ||
} | ||
|
||
|
@@ -993,6 +1015,26 @@ static int _write_regex_to_buffer( | |
return 1; | ||
} | ||
|
||
static int _write_decimal_128_to_buffer(struct module_state *state, PyObject* value, buffer_t buffer, int type_byte) { | ||
const char* data; | ||
PyObject* pystring = PyObject_GetAttr(value, state->_bid_str); | ||
if (!pystring) { | ||
return 0; | ||
} | ||
data = PyBytes_AsString(pystring); | ||
if (!data) { | ||
Py_DECREF(pystring); | ||
return 0; | ||
} | ||
if (!buffer_write_bytes(buffer, data, 16)) { | ||
Py_DECREF(pystring); | ||
return 0; | ||
} | ||
Py_DECREF(pystring); | ||
*(pymongo_buffer_get_buffer(buffer) + type_byte) = 0x13; | ||
return 1; | ||
} | ||
|
||
/* Write a single value to the buffer (also write its type_byte, for which | ||
* space has already been reserved. | ||
* | ||
|
@@ -1206,23 +1248,7 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, | |
case 19: | ||
{ | ||
/* Decimal128 */ | ||
const char* data; | ||
PyObject* pystring = PyObject_GetAttr(value, state->_bid_str); | ||
if (!pystring) { | ||
return 0; | ||
} | ||
data = PyBytes_AsString(pystring); | ||
if (!data) { | ||
Py_DECREF(pystring); | ||
return 0; | ||
} | ||
if (!buffer_write_bytes(buffer, data, 16)) { | ||
Py_DECREF(pystring); | ||
return 0; | ||
} | ||
Py_DECREF(pystring); | ||
*(pymongo_buffer_get_buffer(buffer) + type_byte) = 0x13; | ||
return 1; | ||
return _write_decimal_128_to_buffer(state, value, buffer, type_byte); | ||
} | ||
case 100: | ||
{ | ||
|
@@ -1436,6 +1462,16 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, | |
in_fallback_call); | ||
Py_DECREF(binary_value); | ||
return result; | ||
} else if (options->convert_decimal && _check_decimal(value)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So after case 100 or so and if case 19 wasn't the case, auto-convert ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For consistency with how the rest of |
||
/* Convert decimal.Decimal to Decimal128 */ | ||
PyObject* args = PyTuple_New(1); | ||
|
||
Py_INCREF(value); | ||
PyTuple_SetItem(args, 0, value); | ||
PyObject* converted = PyObject_CallObject(state->Decimal128, args); | ||
Py_DECREF(args); | ||
|
||
return _write_decimal_128_to_buffer(state, converted, buffer, type_byte); | ||
} | ||
|
||
/* Try a custom encoder if one is provided and we have not already | ||
|
Uh oh!
There was an error while loading. Please reload this page.