import time
import datetime as dt
from math import floor
-from typing import Any, Sequence, Union
+from typing import Any, Optional, Sequence, Union
from . import _oids
from .abc import AdaptContext, Buffer
class BinaryBinaryDumper(BytesBinaryDumper):
- def dump(self, obj: Union[Buffer, Binary]) -> Buffer:
+ def dump(self, obj: Union[Buffer, Binary]) -> Optional[Buffer]:
if isinstance(obj, Binary):
return super().dump(obj.obj)
else:
class BinaryTextDumper(BytesDumper):
- def dump(self, obj: Union[Buffer, Binary]) -> Buffer:
+ def dump(self, obj: Union[Buffer, Binary]) -> Optional[Buffer]:
if isinstance(obj, Binary):
return super().dump(obj.obj)
else:
# backslash-escaped.
_re_esc = re.compile(rb'(["\\])')
- def dump(self, obj: List[Any]) -> bytes:
+ def dump(self, obj: List[Any]) -> Optional[Buffer]:
tokens: List[Buffer] = []
needs_quotes = _get_needs_quotes_regexp(self.delimiter).search
return dumper
- def dump(self, obj: List[Any]) -> bytes:
+ def dump(self, obj: List[Any]) -> Optional[Buffer]:
# Postgres won't take unknown for element oid: fall back on text
sub_oid = self.sub_dumper and self.sub_dumper.oid or TEXT_OID
# Copyright (C) 2020 The Psycopg Team
from .. import _oids
+from typing import Optional
from ..pq import Format
from ..abc import AdaptContext
from ..adapt import Buffer, Dumper, Loader
class BoolDumper(Dumper):
oid = _oids.BOOL_OID
- def dump(self, obj: bool) -> bytes:
+ def dump(self, obj: bool) -> Optional[Buffer]:
return b"t" if obj else b"f"
- def quote(self, obj: bool) -> bytes:
+ def quote(self, obj: bool) -> Buffer:
return b"true" if obj else b"false"
format = Format.BINARY
oid = _oids.BOOL_OID
- def dump(self, obj: bool) -> bytes:
+ def dump(self, obj: bool) -> Optional[Buffer]:
return b"\x01" if obj else b"\x00"
from .. import abc
from .. import sql
from .. import postgres
-from ..adapt import Transformer, PyFormat, RecursiveDumper, Loader, Dumper
+from ..adapt import Transformer, PyFormat, RecursiveDumper, Loader, Dumper, Buffer
from .._oids import TEXT_OID
from .._compat import cache
from .._struct import pack_len, unpack_len
# Should be this, but it doesn't work
# oid = _oids.RECORD_OID
- def dump(self, obj: Tuple[Any, ...]) -> bytes:
+ def dump(self, obj: Tuple[Any, ...]) -> Optional[Buffer]:
return self._dump_sequence(obj, b"(", b")", b",")
nfields = len(self._field_types)
self._formats = (PyFormat.from_pq(self.format),) * nfields
- def dump(self, obj: Tuple[Any, ...]) -> bytearray:
+ def dump(self, obj: Tuple[Any, ...]) -> Optional[Buffer]:
out = bytearray(pack_len(len(obj)))
adapted = self._tx.dump_sequence(obj, self._formats)
for i in range(len(obj)):
class DateDumper(Dumper):
oid = _oids.DATE_OID
- def dump(self, obj: date) -> bytes:
+ def dump(self, obj: date) -> Optional[Buffer]:
# NOTE: whatever the PostgreSQL DateStyle input format (DMY, MDY, YMD)
# the YYYY-MM-DD is always understood correctly.
return str(obj).encode()
format = Format.BINARY
oid = _oids.DATE_OID
- def dump(self, obj: date) -> bytes:
+ def dump(self, obj: date) -> Optional[Buffer]:
days = obj.toordinal() - _pg_date_epoch_days
return pack_int4(days)
class _BaseTimeTextDumper(_BaseTimeDumper):
- def dump(self, obj: time) -> bytes:
+ def dump(self, obj: time) -> Optional[Buffer]:
return str(obj).encode()
class TimeTzDumper(_BaseTimeTextDumper):
oid = _oids.TIMETZ_OID
- def dump(self, obj: time) -> bytes:
+ def dump(self, obj: time) -> Optional[Buffer]:
self._get_offset(obj)
return super().dump(obj)
format = Format.BINARY
oid = _oids.TIME_OID
- def dump(self, obj: time) -> bytes:
+ def dump(self, obj: time) -> Optional[Buffer]:
us = obj.microsecond + 1_000_000 * (
obj.second + 60 * (obj.minute + 60 * obj.hour)
)
format = Format.BINARY
oid = _oids.TIMETZ_OID
- def dump(self, obj: time) -> bytes:
+ def dump(self, obj: time) -> Optional[Buffer]:
us = obj.microsecond + 1_000_000 * (
obj.second + 60 * (obj.minute + 60 * obj.hour)
)
class _BaseDatetimeTextDumper(_BaseDatetimeDumper):
- def dump(self, obj: datetime) -> bytes:
+ def dump(self, obj: datetime) -> Optional[Buffer]:
# NOTE: whatever the PostgreSQL DateStyle input format (DMY, MDY, YMD)
# the YYYY-MM-DD is always understood correctly.
return str(obj).encode()
format = Format.BINARY
oid = _oids.TIMESTAMPTZ_OID
- def dump(self, obj: datetime) -> bytes:
+ def dump(self, obj: datetime) -> Optional[Buffer]:
delta = obj - _pg_datetimetz_epoch
micros = delta.microseconds + 1_000_000 * (86_400 * delta.days + delta.seconds)
return pack_int8(micros)
format = Format.BINARY
oid = _oids.TIMESTAMP_OID
- def dump(self, obj: datetime) -> bytes:
+ def dump(self, obj: datetime) -> Optional[Buffer]:
delta = obj - _pg_datetime_epoch
micros = delta.microseconds + 1_000_000 * (86_400 * delta.days + delta.seconds)
return pack_int8(micros)
else:
self._dump_method = self._dump_any
- def dump(self, obj: timedelta) -> bytes:
+ def dump(self, obj: timedelta) -> Optional[Buffer]:
return self._dump_method(self, obj)
@staticmethod
format = Format.BINARY
oid = _oids.INTERVAL_OID
- def dump(self, obj: timedelta) -> bytes:
+ def dump(self, obj: timedelta) -> Optional[Buffer]:
micros = 1_000_000 * obj.seconds + obj.microseconds
return _pack_interval(micros, obj.days, 0)
enum: Type[E]
_dump_map: EnumDumpMap[E]
- def dump(self, value: E) -> Buffer:
+ def dump(self, value: E) -> Optional[Buffer]:
return self._dump_map[value]
super().__init__(cls, context)
self._encoding = conn_encoding(self.connection)
- def dump(self, value: E) -> Buffer:
+ def dump(self, value: E) -> Optional[Buffer]:
return value.name.encode(self._encoding)
super().__init__(cls, context)
self.dumps = self.__class__._dumps
- def dump(self, obj: Any) -> bytes:
+ def dump(self, obj: Any) -> Optional[Buffer]:
if isinstance(obj, _JsonWrapper):
dumps = obj.dumps or self.dumps
obj = obj.obj
format = Format.BINARY
oid = _oids.JSONB_OID
- def dump(self, obj: Any) -> bytes:
+ def dump(self, obj: Any) -> Optional[Buffer]:
return b"\x01" + super().dump(obj)
The dumper can upgrade to one specific for a different range type.
"""
- def dump(self, obj: Multirange[Any]) -> Buffer:
+ def dump(self, obj: Multirange[Any]) -> Optional[Buffer]:
if not obj:
return b"{}"
class MultirangeBinaryDumper(BaseMultirangeDumper):
format = Format.BINARY
- def dump(self, obj: Multirange[Any]) -> Buffer:
+ def dump(self, obj: Multirange[Any]) -> Optional[Buffer]:
item = self._get_item(obj)
if item is not None:
dump = self._tx.get_dumper(item, self._adapt_format).dump
class InterfaceDumper(Dumper):
oid = _oids.INET_OID
- def dump(self, obj: Interface) -> bytes:
+ def dump(self, obj: Interface) -> Optional[Buffer]:
return str(obj).encode()
class NetworkDumper(Dumper):
oid = _oids.CIDR_OID
- def dump(self, obj: Network) -> bytes:
+ def dump(self, obj: Network) -> Optional[Buffer]:
return str(obj).encode()
class AddressBinaryDumper(_AIBinaryDumper):
- def dump(self, obj: Address) -> bytes:
+ def dump(self, obj: Address) -> Optional[Buffer]:
packed = obj.packed
family = PGSQL_AF_INET if obj.version == 4 else PGSQL_AF_INET6
head = bytes((family, obj.max_prefixlen, 0, len(packed)))
class InterfaceBinaryDumper(_AIBinaryDumper):
- def dump(self, obj: Interface) -> bytes:
+ def dump(self, obj: Interface) -> Optional[Buffer]:
packed = obj.packed
family = PGSQL_AF_INET if obj.version == 4 else PGSQL_AF_INET6
head = bytes((family, obj.network.prefixlen, 0, len(packed)))
super().__init__(cls, context)
self._ensure_module()
- def dump(self, obj: Union[Address, Interface]) -> bytes:
+ def dump(self, obj: Union[Address, Interface]) -> Optional[Buffer]:
packed = obj.packed
family = PGSQL_AF_INET if obj.version == 4 else PGSQL_AF_INET6
if isinstance(obj, (IPv4Interface, IPv6Interface)):
format = Format.BINARY
oid = _oids.CIDR_OID
- def dump(self, obj: Network) -> bytes:
+ def dump(self, obj: Network) -> Optional[Buffer]:
packed = obj.network_address.packed
family = PGSQL_AF_INET if obj.version == 4 else PGSQL_AF_INET6
head = bytes((family, obj.prefixlen, 1, len(packed)))
# Copyright (C) 2020 The Psycopg Team
-from ..abc import AdaptContext, NoneType
+from typing import Optional
+
+from ..abc import AdaptContext, NoneType, Buffer
from ..adapt import Dumper
quote(), so it can be used in sql composition.
"""
- def dump(self, obj: None) -> bytes:
+ def dump(self, obj: None) -> Optional[Buffer]:
raise NotImplementedError("NULL is passed to Postgres in other ways")
- def quote(self, obj: None) -> bytes:
+ def quote(self, obj: None) -> Buffer:
return b"NULL"
class _IntDumper(Dumper):
- def dump(self, obj: Any) -> Buffer:
+ def dump(self, obj: Any) -> Optional[Buffer]:
return str(obj).encode()
def quote(self, obj: Any) -> Buffer:
value = self.dump(obj)
+ if value is None:
+ return b"NULL"
return value if obj >= 0 else b" " + value
class _IntOrSubclassDumper(_IntDumper):
- def dump(self, obj: Any) -> Buffer:
+ def dump(self, obj: Any) -> Optional[Buffer]:
t = type(obj)
# Convert to int in order to dump IntEnum or numpy.integer correctly
if t is not int:
class _SpecialValuesDumper(Dumper):
_special: Dict[bytes, bytes] = {}
- def dump(self, obj: Any) -> bytes:
+ def dump(self, obj: Any) -> Optional[Buffer]:
return str(obj).encode()
- def quote(self, obj: Any) -> bytes:
+ def quote(self, obj: Any) -> Buffer:
value = self.dump(obj)
+ if value is None:
+ return b"NULL"
+ if not isinstance(value, bytes):
+ value = bytes(value)
if value in self._special:
return self._special[value]
format = Format.BINARY
oid = _oids.FLOAT8_OID
- def dump(self, obj: float) -> bytes:
+ def dump(self, obj: float) -> Optional[Buffer]:
return pack_float8(obj)
class Float4BinaryDumper(FloatBinaryDumper):
oid = _oids.FLOAT4_OID
- def dump(self, obj: float) -> bytes:
+ def dump(self, obj: float) -> Optional[Buffer]:
return pack_float4(obj)
class DecimalDumper(_SpecialValuesDumper):
oid = _oids.NUMERIC_OID
- def dump(self, obj: Decimal) -> bytes:
+ def dump(self, obj: Decimal) -> Optional[Buffer]:
return dump_decimal_to_text(obj)
_special = {
class IntDumper(Dumper):
- def dump(self, obj: Any) -> bytes:
+ def dump(self, obj: Any) -> Optional[Buffer]:
raise TypeError(
f"{type(self).__name__} is a dispatcher to other dumpers:"
" dump() is not supposed to be called"
class Int2BinaryDumper(Int2Dumper):
format = Format.BINARY
- def dump(self, obj: int) -> bytes:
+ def dump(self, obj: int) -> Optional[Buffer]:
return pack_int2(obj)
class Int4BinaryDumper(Int4Dumper):
format = Format.BINARY
- def dump(self, obj: int) -> bytes:
+ def dump(self, obj: int) -> Optional[Buffer]:
return pack_int4(obj)
class Int8BinaryDumper(Int8Dumper):
format = Format.BINARY
- def dump(self, obj: int) -> bytes:
+ def dump(self, obj: int) -> Optional[Buffer]:
return pack_int8(obj)
class IntNumericBinaryDumper(IntNumericDumper):
format = Format.BINARY
- def dump(self, obj: int) -> Buffer:
+ def dump(self, obj: int) -> Optional[Buffer]:
return dump_int_to_numeric_binary(obj)
class OidBinaryDumper(OidDumper):
format = Format.BINARY
- def dump(self, obj: int) -> bytes:
+ def dump(self, obj: int) -> Optional[Buffer]:
return pack_uint4(obj)
format = Format.BINARY
oid = _oids.NUMERIC_OID
- def dump(self, obj: Decimal) -> Buffer:
+ def dump(self, obj: Decimal) -> Optional[Buffer]:
return dump_decimal_to_numeric_binary(obj)
_MixedNumericDumper.int_classes = int
@abstractmethod
- def dump(self, obj: Union[Decimal, int, "numpy.integer[Any]"]) -> Buffer: ...
+ def dump(
+ self, obj: Union[Decimal, int, "numpy.integer[Any]"]
+ ) -> Optional[Buffer]: ...
class NumericDumper(_MixedNumericDumper):
- def dump(self, obj: Union[Decimal, int, "numpy.integer[Any]"]) -> Buffer:
+ def dump(self, obj: Union[Decimal, int, "numpy.integer[Any]"]) -> Optional[Buffer]:
if isinstance(obj, self.int_classes):
return str(obj).encode()
elif isinstance(obj, Decimal):
class NumericBinaryDumper(_MixedNumericDumper):
format = Format.BINARY
- def dump(self, obj: Union[Decimal, int, "numpy.integer[Any]"]) -> Buffer:
+ def dump(self, obj: Union[Decimal, int, "numpy.integer[Any]"]) -> Optional[Buffer]:
if type(obj) is int:
return dump_int_to_numeric_binary(obj)
elif isinstance(obj, Decimal):
The dumper can upgrade to one specific for a different range type.
"""
- def dump(self, obj: Range[Any]) -> Buffer:
+ def dump(self, obj: Range[Any]) -> Optional[Buffer]:
item = self._get_item(obj)
if item is not None:
dump = self._tx.get_dumper(item, self._adapt_format).dump
class RangeBinaryDumper(BaseRangeDumper):
format = Format.BINARY
- def dump(self, obj: Range[Any]) -> Buffer:
+ def dump(self, obj: Range[Any]) -> Optional[Buffer]:
item = self._get_item(obj)
if item is not None:
dump = self._tx.get_dumper(item, self._adapt_format).dump
class BaseGeometryBinaryDumper(Dumper):
format = Format.BINARY
- def dump(self, obj: "BaseGeometry") -> bytes:
+ def dump(self, obj: "BaseGeometry") -> Optional[Buffer]:
return dumps(obj) # type: ignore
class BaseGeometryDumper(Dumper):
- def dump(self, obj: "BaseGeometry") -> bytes:
+ def dump(self, obj: "BaseGeometry") -> Optional[Buffer]:
return dumps(obj, hex=True).encode() # type: ignore
format = Format.BINARY
- def dump(self, obj: str) -> bytes:
+ def dump(self, obj: str) -> Optional[Buffer]:
# the server will raise DataError subclass if the string contains 0x00
return obj.encode(self._encoding)
Subclasses shall specify the oids of real types (text, varchar, name...).
"""
- def dump(self, obj: str) -> bytes:
+ def dump(self, obj: str) -> Optional[Buffer]:
if "\x00" in obj:
raise DataError("PostgreSQL text fields cannot contain NUL (0x00) bytes")
else:
super().__init__(cls, context)
self._esc = Escaping(self.connection.pgconn if self.connection else None)
- def dump(self, obj: Buffer) -> Buffer:
+ def dump(self, obj: Buffer) -> Optional[Buffer]:
return self._esc.escape_bytea(obj)
- def quote(self, obj: Buffer) -> bytes:
+ def quote(self, obj: Buffer) -> Buffer:
escaped = self.dump(obj)
+ if escaped is None:
+ return b"NULL"
# We cannot use the base quoting because escape_bytea already returns
# the quotes content. if scs is off it will escape the backslashes in
format = Format.BINARY
oid = _oids.BYTEA_OID
- def dump(self, obj: Buffer) -> Buffer:
+ def dump(self, obj: Buffer) -> Optional[Buffer]:
return obj
class UUIDDumper(Dumper):
oid = _oids.UUID_OID
- def dump(self, obj: "uuid.UUID") -> bytes:
+ def dump(self, obj: "uuid.UUID") -> Optional[Buffer]:
return obj.hex.encode()
class UUIDBinaryDumper(UUIDDumper):
format = Format.BINARY
- def dump(self, obj: "uuid.UUID") -> bytes:
+ def dump(self, obj: "uuid.UUID") -> Optional[Buffer]:
return obj.bytes
"""
raise NotImplementedError()
- def dump(self, obj):
+ def dump(self, obj) -> Optional[Buffer]:
"""Return the Postgres representation of *obj* as Python array of bytes"""
cdef rv = PyByteArray_FromStringAndSize("", 0)
cdef Py_ssize_t length = self.cdump(obj, rv, 0)
PyByteArray_Resize(rv, length)
return rv
- def quote(self, obj):
+ def quote(self, obj) -> Buffer:
cdef char *ptr
cdef char *ptr_out
cdef Py_ssize_t length
return 1
- def quote(self, obj: bool) -> bytes:
+ def quote(self, obj: bool) -> Optional[Buffer]:
if obj is True:
return b"true"
elif obj is False:
cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
return dump_int_to_text(obj, rv, offset)
- def quote(self, obj) -> bytearray:
+ def quote(self, obj) -> Optional[Buffer]:
cdef Py_ssize_t length
rv = PyByteArray_FromStringAndSize("", 0)
PyMem_Free(out)
return length
- def quote(self, obj) -> bytes:
+ def quote(self, obj) -> Optional[Buffer]:
value = bytes(self.dump(obj))
cdef PyObject *ptr = PyDict_GetItem(_special_float, value)
if ptr != NULL:
cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
return dump_decimal_to_text(obj, rv, offset)
- def quote(self, obj) -> bytes:
+ def quote(self, obj) -> Optional[Buffer]:
value = bytes(self.dump(obj))
cdef PyObject *ptr = PyDict_GetItem(_special_decimal, value)
if ptr != NULL:
libpq.PQfreemem(out)
return len_out
- def quote(self, obj):
+ def quote(self, obj) -> Buffer:
cdef size_t len_out
cdef unsigned char *out
cdef char *ptr
class MyStrDumper(BaseDumper):
def dump(self, obj):
- return super().dump(obj) * 2
+ rv = super().dump(obj)
+ assert rv
+ return bytes(rv) * 2
conn.adapters.register_dumper(str, MyStrDumper)
class MyStrDumper(BaseDumper):
def dump(self, obj):
- return super().dump(obj) * 2
+ rv = super().dump(obj)
+ assert rv
+ return bytes(rv) * 2
aconn.adapters.register_dumper(str, MyStrDumper)