Skip to content
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

features #32

Merged
merged 2 commits into from
May 9, 2024
Merged
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,5 @@ deploy_key*
!.ci/deploy_key.enc
/core
cython_debug

temp
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v2.3.0
**New features:**
* Added support for [interval types](https://www.tarantool.io/en/doc/latest/reference/reference_lua/datetime/interval_object/) [#30](https://github.com/igorcoding/asynctnt/issues/30)
* Added ability to retrieve IProto features [available](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_iproto/feature/) in Tarantool using `conn.features` property


## v2.2.0
**New features:**
* Implemented ability to send update/upsert requests with field names when schema is disabled (`fetch_schema=False`) and when fields are not found in the schema (good example of this case is using json path like `data.inner1.inner2.key1` as a key)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ Documentation is available [here](https://igorcoding.github.io/asynctnt).
* Full support for [SQL](https://www.tarantool.io/en/doc/latest/tutorials/sql_tutorial/),
including [prepared statements](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_sql/prepare/).
* Support for [interactive transaction](https://www.tarantool.io/en/doc/latest/book/box/atomic/txn_mode_mvcc/) via Tarantool streams.
* Support of `Decimal`, `UUID` and `datetime` types natively.
* Support of `Decimal`, `UUID`,`datetime` types natively.
* Support for [interval types](https://www.tarantool.io/en/doc/latest/reference/reference_lua/datetime/interval_object/).
* Support for parsing [custom errors](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_error/new/).
* **Schema fetching** on connection establishment, so you can use spaces and
indexes names rather than their ids, and **auto refetching** if schema in
Expand Down
4 changes: 3 additions & 1 deletion asynctnt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

from .connection import Connection, connect
from .iproto.protocol import (
Adjust,
Db,
Field,
IProtoError,
IProtoErrorStackFrame,
Iterator,
Metadata,
MPInterval,
PushIterator,
Response,
Schema,
Expand All @@ -16,4 +18,4 @@
TarantoolTuple,
)

__version__ = "2.2.0"
__version__ = "2.3.0"
12 changes: 10 additions & 2 deletions asynctnt/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ async def reconnect(self):
await self.disconnect()
await self.connect()

async def __aenter__(self):
async def __aenter__(self) -> "Connection":
"""
Executed on entering the async with section.
Connects to Tarantool instance.
Expand Down Expand Up @@ -606,7 +606,7 @@ def _normalize_api(self):
Api.call = Api.call16
Connection.call = Connection.call16

if self.version < (2, 10): # pragma: nocover
if not self.features.streams: # pragma: nocover

def stream_stub(_):
raise TarantoolError("streams are available only in Tarantool 2.10+")
Expand All @@ -627,6 +627,14 @@ def stream(self) -> Stream:
stream._set_db(db)
return stream

@property
def features(self) -> protocol.IProtoFeatures:
"""
Lookup available Tarantool features - https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_iproto/feature/
:return:
"""
return self._protocol.features


async def connect(**kwargs) -> Connection:
"""
Expand Down
1 change: 1 addition & 0 deletions asynctnt/iproto/buffer.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ cdef class WriteBuffer:
cdef char *mp_encode_decimal(self, char *p, object value) except NULL
cdef char *mp_encode_uuid(self, char *p, object value) except NULL
cdef char *mp_encode_datetime(self, char *p, object value) except NULL
cdef char *mp_encode_interval(self, char *p, MPInterval value) except NULL
cdef char *mp_encode_array(self, char *p, uint32_t len) except NULL
cdef char *mp_encode_map(self, char *p, uint32_t len) except NULL
cdef char *mp_encode_list(self, char *p, list arr) except NULL
Expand Down
17 changes: 17 additions & 0 deletions asynctnt/iproto/buffer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,20 @@ cdef class WriteBuffer:
self._length += (p - begin)
return p

cdef char *mp_encode_interval(self, char *p, MPInterval value) except NULL:
cdef:
char *begin
char *data_p
uint32_t length

length = interval_len(value)
p = begin = self._ensure_allocated(p, mp_sizeof_ext(length))
p = mp_encode_extl(p, tarantool.MP_INTERVAL, length)
p = interval_encode(p, value)

self._length += (p - begin)
return p

cdef char *mp_encode_array(self, char *p, uint32_t len) except NULL:
cdef char *begin
p = begin = self._ensure_allocated(p, mp_sizeof_array(len))
Expand Down Expand Up @@ -406,6 +420,9 @@ cdef class WriteBuffer:
elif isinstance(o, datetime):
return self.mp_encode_datetime(p, o)

elif isinstance(o, MPInterval):
return self.mp_encode_interval(p, <MPInterval> o)

elif isinstance(o, Decimal):
return self.mp_encode_decimal(p, o)

Expand Down
4 changes: 4 additions & 0 deletions asynctnt/iproto/cmsgpuck.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ cdef extern from "../../third_party/msgpuck/msgpuck.h":
cdef char *mp_store_u32(char *data, uint32_t val)
cdef char *mp_store_u64(char *data, uint64_t val)

cdef ptrdiff_t mp_check_uint(const char *cur, const char *end)
cdef ptrdiff_t mp_check_int(const char *cur, const char *end)

cdef mp_type mp_typeof(const char c)

cdef uint32_t mp_sizeof_array(uint32_t size)
Expand All @@ -43,6 +46,7 @@ cdef extern from "../../third_party/msgpuck/msgpuck.h":
cdef uint32_t mp_sizeof_int(int64_t num)
cdef char *mp_encode_int(char *data, int64_t num)
cdef int64_t mp_decode_int(const char **data)
cdef int mp_read_int64(const char **data, int64_t *ret)

cdef uint32_t mp_sizeof_float(float num)
cdef char *mp_encode_float(char *data, float num)
Expand Down
32 changes: 0 additions & 32 deletions asynctnt/iproto/ext.pxd

This file was deleted.

18 changes: 18 additions & 0 deletions asynctnt/iproto/ext/datetime.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from cpython.datetime cimport datetime
from libc.stdint cimport int16_t, int32_t, int64_t, uint32_t


cdef struct IProtoDateTime:
int64_t seconds
int32_t nsec
int16_t tzoffset
int16_t tzindex

cdef void datetime_zero(IProtoDateTime *dt)
cdef uint32_t datetime_len(IProtoDateTime *dt)
cdef char *datetime_encode(char *p, IProtoDateTime *dt) except NULL
cdef int datetime_decode(const char ** p,
uint32_t length,
IProtoDateTime *dt) except -1
cdef void datetime_from_py(datetime ob, IProtoDateTime *dt)
cdef object datetime_to_py(IProtoDateTime *dt)
87 changes: 87 additions & 0 deletions asynctnt/iproto/ext/datetime.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
cimport cpython.datetime
from cpython.datetime cimport PyDateTimeAPI, datetime, datetime_tzinfo, timedelta_new
from libc.stdint cimport uint32_t
from libc.string cimport memcpy


cdef inline void datetime_zero(IProtoDateTime *dt):
dt.seconds = 0
dt.nsec = 0
dt.tzoffset = 0
dt.tzindex = 0

cdef inline uint32_t datetime_len(IProtoDateTime *dt):
cdef uint32_t sz
sz = sizeof(int64_t)
if dt.nsec != 0 or dt.tzoffset != 0 or dt.tzindex != 0:
return sz + DATETIME_TAIL_SZ
return sz

cdef char *datetime_encode(char *p, IProtoDateTime *dt) except NULL:
store_u64(p, dt.seconds)
p += sizeof(dt.seconds)
if dt.nsec != 0 or dt.tzoffset != 0 or dt.tzindex != 0:
memcpy(p, &dt.nsec, DATETIME_TAIL_SZ)
p += DATETIME_TAIL_SZ
return p

cdef int datetime_decode(
const char ** p,
uint32_t length,
IProtoDateTime *dt
) except -1:
delta = None
tz = None

dt.seconds = load_u64(p[0])
p[0] += sizeof(dt.seconds)
length -= sizeof(dt.seconds)

if length == 0:
return 0

if length != DATETIME_TAIL_SZ:
raise ValueError("invalid datetime size. got {} extra bytes".format(
length
))

dt.nsec = load_u32(p[0])
p[0] += 4
dt.tzoffset = load_u16(p[0])
p[0] += 2
dt.tzindex = load_u16(p[0])
p[0] += 2

cdef void datetime_from_py(datetime ob, IProtoDateTime *dt):
cdef:
double ts
int offset
ts = <double> ob.timestamp()
dt.seconds = <int64_t> ts
dt.nsec = <int32_t> ((ts - <double> dt.seconds) * 1000000) * 1000
if dt.nsec < 0:
# correction for negative dates
dt.seconds -= 1
dt.nsec += 1000000000

if datetime_tzinfo(ob) is not None:
offset = ob.utcoffset().total_seconds()
dt.tzoffset = <int16_t> (offset / 60)

cdef object datetime_to_py(IProtoDateTime *dt):
cdef:
double timestamp
object tz

tz = None

if dt.tzoffset != 0:
delta = timedelta_new(0, <int> dt.tzoffset * 60, 0)
tz = timezone_new(delta)

timestamp = dt.seconds + (<double> dt.nsec) / 1e9
return PyDateTimeAPI.DateTime_FromTimestamp(
<PyObject *>PyDateTimeAPI.DateTimeType,
(timestamp,) if tz is None else (timestamp, tz),
NULL,
)
14 changes: 14 additions & 0 deletions asynctnt/iproto/ext/decimal.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from libc cimport math
from libc.stdint cimport uint8_t, uint32_t


cdef inline uint32_t bcd_len(uint32_t digits_len):
return <uint32_t> math.floor(digits_len / 2) + 1

cdef uint32_t decimal_len(int exponent, uint32_t digits_count)
cdef char *decimal_encode(char *p,
uint32_t digits_count,
uint8_t sign,
tuple digits,
int exponent) except NULL
cdef object decimal_decode(const char ** p, uint32_t length)
91 changes: 0 additions & 91 deletions asynctnt/iproto/ext.pyx → asynctnt/iproto/ext/decimal.pyx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
cimport cpython.datetime
from cpython.datetime cimport PyDateTimeAPI, datetime, datetime_tzinfo, timedelta_new
from libc.stdint cimport uint32_t
from libc.string cimport memcpy

from decimal import Decimal
from uuid import UUID


cdef uint32_t decimal_len(int exponent, uint32_t digits_count):
Expand Down Expand Up @@ -127,90 +123,3 @@ cdef object decimal_decode(const char ** p, uint32_t length):
p[0] += length

return Decimal((<object> <int> sign, digits, <object> exponent))

cdef object uuid_decode(const char ** p, uint32_t length):
data = cpython.bytes.PyBytes_FromStringAndSize(p[0], length)
p[0] += length
return UUID(bytes=data)

cdef inline void datetime_zero(IProtoDateTime *dt):
dt.seconds = 0
dt.nsec = 0
dt.tzoffset = 0
dt.tzindex = 0

cdef inline uint32_t datetime_len(IProtoDateTime *dt):
cdef uint32_t sz
sz = sizeof(int64_t)
if dt.nsec != 0 or dt.tzoffset != 0 or dt.tzindex != 0:
return sz + DATETIME_TAIL_SZ
return sz

cdef char *datetime_encode(char *p, IProtoDateTime *dt) except NULL:
store_u64(p, dt.seconds)
p += sizeof(dt.seconds)
if dt.nsec != 0 or dt.tzoffset != 0 or dt.tzindex != 0:
memcpy(p, &dt.nsec, DATETIME_TAIL_SZ)
p += DATETIME_TAIL_SZ
return p

cdef int datetime_decode(
const char ** p,
uint32_t length,
IProtoDateTime *dt
) except -1:
delta = None
tz = None

dt.seconds = load_u64(p[0])
p[0] += sizeof(dt.seconds)
length -= sizeof(dt.seconds)

if length == 0:
return 0

if length != DATETIME_TAIL_SZ:
raise ValueError("invalid datetime size. got {} extra bytes".format(
length
))

dt.nsec = load_u32(p[0])
p[0] += 4
dt.tzoffset = load_u16(p[0])
p[0] += 2
dt.tzindex = load_u16(p[0])
p[0] += 2

cdef void datetime_from_py(datetime ob, IProtoDateTime *dt):
cdef:
double ts
int offset
ts = <double> ob.timestamp()
dt.seconds = <int64_t> ts
dt.nsec = <int32_t> ((ts - <double> dt.seconds) * 1000000) * 1000
if dt.nsec < 0:
# correction for negative dates
dt.seconds -= 1
dt.nsec += 1000000000

if datetime_tzinfo(ob) is not None:
offset = ob.utcoffset().total_seconds()
dt.tzoffset = <int16_t> (offset / 60)

cdef object datetime_to_py(IProtoDateTime *dt):
cdef:
double timestamp
object tz

tz = None

if dt.tzoffset != 0:
delta = timedelta_new(0, <int> dt.tzoffset * 60, 0)
tz = timezone_new(delta)

timestamp = dt.seconds + (<double> dt.nsec) / 1e9
return PyDateTimeAPI.DateTime_FromTimestamp(
<PyObject *>PyDateTimeAPI.DateTimeType,
(timestamp,) if tz is None else (timestamp, tz),
NULL,
)
Loading
Loading