Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# A clang-format style that approximates Python's PEP 7
BasedOnStyle: Google
AlwaysBreakAfterReturnType: All
AllowShortIfStatementsOnASingleLine: false
AlignAfterOpenBracket: Align
BreakBeforeBraces: Stroustrup
ColumnLimit: 95
DerivePointerAlignment: false
IndentWidth: 4
Language: Cpp
PointerAlignment: Right
ReflowComments: true
SpaceBeforeParens: ControlStatements
SpacesInParentheses: false
TabWidth: 4
UseTab: Never
SortIncludes: false
4 changes: 4 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Diagnostics:
Includes:
IgnoreHeader:
- "pythoncapi_compat.*\\.h"
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
# job.
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]
os: [ubuntu-latest, macos-latest, windows-latest]
loop: [asyncio, uvloop]
exclude:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ docs/_build
/.pytest_cache/
/.eggs
/.vscode
/.zed
/.mypy_cache
/.venv*
/.tox
/compile_commands.json
4 changes: 2 additions & 2 deletions asyncpg/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2751,8 +2751,8 @@ def _check_record_class(record_class):
and issubclass(record_class, protocol.Record)
):
if (
record_class.__new__ is not object.__new__
or record_class.__init__ is not object.__init__
record_class.__new__ is not protocol.Record.__new__
or record_class.__init__ is not protocol.Record.__init__
):
raise exceptions.InterfaceError(
'record_class must not redefine __new__ or __init__'
Expand Down
3 changes: 2 additions & 1 deletion asyncpg/protocol/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@

from __future__ import annotations

from .protocol import Protocol, Record, NO_TIMEOUT, BUILTIN_TYPE_NAME_MAP
from .protocol import Protocol, NO_TIMEOUT, BUILTIN_TYPE_NAME_MAP
from ..record import Record
12 changes: 12 additions & 0 deletions asyncpg/protocol/codecs/base.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ ctypedef object (*codec_decode_func)(Codec codec,
FRBuffer *buf)


cdef class CodecMap:
cdef:
void** binary_codec_map
void** text_codec_map
dict extra_codecs

cdef inline void *get_binary_codec_ptr(self, uint32_t idx)
cdef inline void set_binary_codec_ptr(self, uint32_t idx, void *ptr)
cdef inline void *get_text_codec_ptr(self, uint32_t idx)
cdef inline void set_text_codec_ptr(self, uint32_t idx, void *ptr)


cdef enum CodecType:
CODEC_UNDEFINED = 0
CODEC_C = 1
Expand Down
51 changes: 38 additions & 13 deletions asyncpg/protocol/codecs/base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,31 @@ import asyncpg
from asyncpg import exceptions


cdef void* binary_codec_map[(MAXSUPPORTEDOID + 1) * 2]
cdef void* text_codec_map[(MAXSUPPORTEDOID + 1) * 2]
cdef dict EXTRA_CODECS = {}
# The class indirection is needed because Cython
# does not (as of 3.1.0) store global cdef variables
# in module state.
@cython.final
cdef class CodecMap:

def __cinit__(self):
self.extra_codecs = {}
self.binary_codec_map = <void **>cpython.PyMem_Calloc((MAXSUPPORTEDOID + 1) * 2, sizeof(void *));
self.text_codec_map = <void **>cpython.PyMem_Calloc((MAXSUPPORTEDOID + 1) * 2, sizeof(void *));

cdef inline void *get_binary_codec_ptr(self, uint32_t idx):
return <void*>self.binary_codec_map[idx]

cdef inline void set_binary_codec_ptr(self, uint32_t idx, void *ptr):
self.binary_codec_map[idx] = ptr

cdef inline void *get_text_codec_ptr(self, uint32_t idx):
return <void*>self.text_codec_map[idx]

cdef inline void set_text_codec_ptr(self, uint32_t idx, void *ptr):
self.text_codec_map[idx] = ptr


codec_map = CodecMap()


@cython.final
Expand Down Expand Up @@ -67,7 +89,7 @@ cdef class Codec:
)

if element_names is not None:
self.record_desc = record.ApgRecordDesc_New(
self.record_desc = RecordDescriptor(
element_names, tuple(element_names))
else:
self.record_desc = None
Expand Down Expand Up @@ -271,7 +293,7 @@ cdef class Codec:
schema=self.schema,
data_type=self.name,
)
result = record.ApgRecord_New(asyncpg.Record, self.record_desc, elem_count)
result = self.record_desc.make_record(asyncpg.Record, elem_count)
for i in range(elem_count):
elem_typ = self.element_type_oids[i]
received_elem_typ = <uint32_t>hton.unpack_int32(frb_read(buf, 4))
Expand Down Expand Up @@ -301,7 +323,7 @@ cdef class Codec:
settings, frb_slice_from(&elem_buf, buf, elem_len))

cpython.Py_INCREF(elem)
record.ApgRecord_SET_ITEM(result, i, elem)
recordcapi.ApgRecord_SET_ITEM(result, i, elem)

return result

Expand Down Expand Up @@ -811,9 +833,9 @@ cdef inline Codec get_core_codec(
if oid > MAXSUPPORTEDOID:
return None
if format == PG_FORMAT_BINARY:
ptr = binary_codec_map[oid * xformat]
ptr = (<CodecMap>codec_map).get_binary_codec_ptr(oid * xformat)
elif format == PG_FORMAT_TEXT:
ptr = text_codec_map[oid * xformat]
ptr = (<CodecMap>codec_map).get_text_codec_ptr(oid * xformat)

if ptr is NULL:
return None
Expand All @@ -839,7 +861,10 @@ cdef inline Codec get_any_core_codec(


cdef inline int has_core_codec(uint32_t oid):
return binary_codec_map[oid] != NULL or text_codec_map[oid] != NULL
return (
(<CodecMap>codec_map).get_binary_codec_ptr(oid) != NULL
or (<CodecMap>codec_map).get_text_codec_ptr(oid) != NULL
)


cdef register_core_codec(uint32_t oid,
Expand Down Expand Up @@ -867,9 +892,9 @@ cdef register_core_codec(uint32_t oid,
cpython.Py_INCREF(codec) # immortalize

if format == PG_FORMAT_BINARY:
binary_codec_map[oid * xformat] = <void*>codec
(<CodecMap>codec_map).set_binary_codec_ptr(oid * xformat, <void*>codec)
elif format == PG_FORMAT_TEXT:
text_codec_map[oid * xformat] = <void*>codec
(<CodecMap>codec_map).set_text_codec_ptr(oid * xformat, <void*>codec)
else:
raise exceptions.InternalClientError(
'invalid data format: {}'.format(format))
Expand All @@ -888,8 +913,8 @@ cdef register_extra_codec(str name,
codec = Codec(INVALIDOID)
codec.init(name, None, kind, CODEC_C, format, PG_XFORMAT_OBJECT,
encode, decode, None, None, None, None, None, None, None, 0)
EXTRA_CODECS[name, format] = codec
(<CodecMap>codec_map).extra_codecs[name, format] = codec


cdef inline Codec get_extra_codec(str name, ServerDataFormat format):
return EXTRA_CODECS.get((name, format))
return (<CodecMap>codec_map).extra_codecs.get((name, format))
6 changes: 3 additions & 3 deletions asyncpg/protocol/coreproto.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import hashlib
include "scram.pyx"


cdef dict AUTH_METHOD_NAME = {
AUTH_METHOD_NAME = {
AUTH_REQUIRED_KERBEROS: 'kerberosv5',
AUTH_REQUIRED_PASSWORD: 'password',
AUTH_REQUIRED_PASSWORDMD5: 'md5',
Expand Down Expand Up @@ -1229,5 +1229,5 @@ cdef class CoreProtocol:
pass


cdef bytes SYNC_MESSAGE = bytes(WriteBuffer.new_message(b'S').end_message())
cdef bytes FLUSH_MESSAGE = bytes(WriteBuffer.new_message(b'H').end_message())
SYNC_MESSAGE = bytes(WriteBuffer.new_message(b'S').end_message())
FLUSH_MESSAGE = bytes(WriteBuffer.new_message(b'H').end_message())
2 changes: 1 addition & 1 deletion asyncpg/protocol/encodings.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
https://www.postgresql.org/docs/current/static/multibyte.html#CHARSET-TABLE
'''

cdef dict ENCODINGS_MAP = {
ENCODINGS_MAP = {
'abc': 'cp1258',
'alt': 'cp866',
'euc_cn': 'euccn',
Expand Down
2 changes: 1 addition & 1 deletion asyncpg/protocol/pgtypes.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ DEF ANYCOMPATIBLEARRAYOID = 5078
DEF ANYCOMPATIBLENONARRAYOID = 5079
DEF ANYCOMPATIBLERANGEOID = 5080

cdef ARRAY_TYPES = (_TEXTOID, _OIDOID,)
ARRAY_TYPES = {_TEXTOID, _OIDOID}

BUILTIN_TYPE_OID_MAP = {
ABSTIMEOID: 'abstime',
Expand Down
8 changes: 4 additions & 4 deletions asyncpg/protocol/prepared_stmt.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ cdef class PreparedStatementState:
return

if self.cols_num == 0:
self.cols_desc = record.ApgRecordDesc_New({}, ())
self.cols_desc = RecordDescriptor({}, ())
return

cols_mapping = collections.OrderedDict()
Expand All @@ -252,7 +252,7 @@ cdef class PreparedStatementState:

codecs.append(codec)

self.cols_desc = record.ApgRecordDesc_New(
self.cols_desc = RecordDescriptor(
cols_mapping, tuple(cols_names))

self.rows_codecs = tuple(codecs)
Expand Down Expand Up @@ -310,7 +310,7 @@ cdef class PreparedStatementState:
'different from what was described ({})'.format(
fnum, self.cols_num))

dec_row = record.ApgRecord_New(self.record_class, self.cols_desc, fnum)
dec_row = self.cols_desc.make_record(self.record_class, fnum)
for i in range(fnum):
flen = hton.unpack_int32(frb_read(&rbuf, 4))

Expand All @@ -333,7 +333,7 @@ cdef class PreparedStatementState:
frb_set_len(&rbuf, bl - flen)

cpython.Py_INCREF(val)
record.ApgRecord_SET_ITEM(dec_row, i, val)
recordcapi.ApgRecord_SET_ITEM(dec_row, i, val)

if frb_get_len(&rbuf) != 0:
raise BufferError('unexpected trailing {} bytes in buffer'.format(
Expand Down
22 changes: 2 additions & 20 deletions asyncpg/protocol/protocol.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import asyncio
import asyncio.protocols
import hmac
from codecs import CodecInfo
from collections.abc import Callable, Iterable, Iterator, Sequence
from collections.abc import Callable, Iterable, Sequence
from hashlib import md5, sha256
from typing import (
Any,
Expand All @@ -22,8 +22,8 @@ import asyncpg.pgproto.pgproto
from ..connect_utils import _ConnectionParameters
from ..pgproto.pgproto import WriteBuffer
from ..types import Attribute, Type
from ..record import Record

_T = TypeVar('_T')
_Record = TypeVar('_Record', bound=Record)
_OtherRecord = TypeVar('_OtherRecord', bound=Record)
_PreparedStatementState = TypeVar(
Expand Down Expand Up @@ -254,24 +254,6 @@ class DataCodecConfig:

class Protocol(BaseProtocol[_Record], asyncio.protocols.Protocol): ...

class Record:
@overload
def get(self, key: str) -> Any | None: ...
@overload
def get(self, key: str, default: _T) -> Any | _T: ...
def items(self) -> Iterator[tuple[str, Any]]: ...
def keys(self) -> Iterator[str]: ...
def values(self) -> Iterator[Any]: ...
@overload
def __getitem__(self, index: str) -> Any: ...
@overload
def __getitem__(self, index: int) -> Any: ...
@overload
def __getitem__(self, index: slice) -> tuple[Any, ...]: ...
def __iter__(self) -> Iterator[Any]: ...
def __contains__(self, x: object) -> bool: ...
def __len__(self) -> int: ...

class Timer:
def __init__(self, budget: float | None) -> None: ...
def __enter__(self) -> None: ...
Expand Down
14 changes: 6 additions & 8 deletions asyncpg/protocol/protocol.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ from asyncpg.pgproto.pgproto cimport (

from asyncpg.pgproto cimport pgproto
from asyncpg.protocol cimport cpythonx
from asyncpg.protocol cimport record
from asyncpg.protocol cimport recordcapi

from libc.stdint cimport int8_t, uint8_t, int16_t, uint16_t, \
int32_t, uint32_t, int64_t, uint64_t, \
Expand All @@ -46,6 +46,7 @@ from asyncpg import types as apg_types
from asyncpg import exceptions as apg_exc

from asyncpg.pgproto cimport hton
from asyncpg.record import Record, RecordDescriptor


include "consts.pxi"
Expand Down Expand Up @@ -1049,17 +1050,14 @@ def _create_record(object mapping, tuple elems):
int32_t i

if mapping is None:
desc = record.ApgRecordDesc_New({}, ())
desc = RecordDescriptor({}, ())
else:
desc = record.ApgRecordDesc_New(
desc = RecordDescriptor(
mapping, tuple(mapping) if mapping else ())

rec = record.ApgRecord_New(Record, desc, len(elems))
rec = desc.make_record(Record, len(elems))
for i in range(len(elems)):
elem = elems[i]
cpython.Py_INCREF(elem)
record.ApgRecord_SET_ITEM(rec, i, elem)
recordcapi.ApgRecord_SET_ITEM(rec, i, elem)
return rec


Record = <object>record.ApgRecord_InitTypes()
Loading
Loading