diff --git a/.github/workflows/actions.yaml b/.github/workflows/actions.yaml
index 39984e8..1ee75b5 100644
--- a/.github/workflows/actions.yaml
+++ b/.github/workflows/actions.yaml
@@ -7,17 +7,17 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, macos-latest ]
- python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', 'pypy3.10']
+ python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.14t', 'pypy3.10', 'pypy3.11']
tarantool: ['1.10', '2', '3']
exclude:
- os: macos-latest
tarantool: '1.10'
- os: macos-latest
tarantool: '2'
- - os: macos-latest
- python-version: '3.7'
- python-version: 'pypy3.10'
tarantool: '1.10'
+ - python-version: 'pypy3.11'
+ tarantool: '1.10'
runs-on: ${{ matrix.os }}
@@ -25,6 +25,7 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
+
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
@@ -40,24 +41,41 @@ jobs:
if: matrix.os == 'macos-latest'
run: brew install tarantool
+ - name: Install uv
+ uses: astral-sh/setup-uv@v4
+
- name: Install dependencies
- run: |
- python -m pip install --upgrade pip setuptools wheel coveralls
+ run: uv sync --extra test
+
+ - name: Run ruff check
+ run: uv run ruff check .
+
+ - name: Run ruff format check
+ run: uv run ruff format --check .
+
- name: Run tests
- run: |
- if [[ "$RUNNER_OS" == "Linux" && ${{ matrix.python-version }} == "3.12" && ${{ matrix.tarantool }} == "3" ]]; then
- make build && make test
- make clean && make debug && make coverage
- # coveralls
- else
- make build && make lint && make quicktest
- fi
+ run: uv run pytest .
+
+ - name: Run tests with uvloop
+ if: "!contains(matrix.python-version, 'pypy')"
+ env:
+ USE_UVLOOP: "1"
+ run: uv run pytest .
+
+ - name: Run coverage tests
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14' && matrix.tarantool == '3'
env:
+ ASYNCTNT_DEBUG: "1"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}"
+ run: |
+ make clean
+ uv pip install -e '.[test]'
+ uv run pytest --cov
+ ./scripts/run_until_success.sh uv run coverage report -m
+ ./scripts/run_until_success.sh uv run coverage html
build-wheels:
name: Build wheels on ${{ matrix.os }}
- if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
runs-on: ${{ matrix.os }}
strategy:
matrix:
@@ -71,19 +89,33 @@ jobs:
submodules: recursive
- uses: actions/setup-python@v5
+ with:
+ python-version: '3.14'
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v4
- name: Install cibuildwheel
- run: python -m pip install --upgrade cibuildwheel
+ run: uv tool install cibuildwheel
+
+ - name: Install pyproject-build
+ run: uv tool install build
+
+ - name: Build source archive
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ pyproject-build -s .
- name: Build wheels
- run: python -m cibuildwheel --output-dir wheelhouse
+ run: cibuildwheel --output-dir dist
env:
- CIBW_BUILD: "cp37-* cp38-* cp39-* cp310-* cp311-* cp312-* cp313-* pp310-*"
+ CIBW_ENABLE: pypy pypy-eol
+ CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-* cp314-* cp314t-* pp310-* pp311-*"
- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}
- path: ./wheelhouse/*.whl
+ path: ./dist/*
publish:
name: Publish wheels
@@ -91,55 +123,66 @@ jobs:
needs:
- build-wheels
runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ url: https://pypi.org/p/asynctnt
+ permissions:
+ id-token: write # Required for trusted publishing
+ contents: write # Required for releases
steps:
- name: Get tag
id: get_tag
run: echo ::set-output name=TAG::${GITHUB_REF/refs\/tags\//}
+
- run: echo "Current tag is ${{ steps.get_tag.outputs.TAG }}"
+
- uses: actions/checkout@v4
with:
submodules: recursive
+
- name: Set up Python
uses: actions/setup-python@v5
with:
- python-version: '3.12'
+ python-version: '3.14'
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v4
- name: Install dependencies
run: |
- python -m pip install --upgrade pip setuptools wheel twine build
+ uv pip install --upgrade build
- uses: actions/download-artifact@v4
with:
name: wheels-ubuntu-latest
- path: wheels-ubuntu
+ path: dist
- uses: actions/download-artifact@v4
with:
name: wheels-macos-latest
- path: wheels-macos
+ path: dist
- uses: actions/download-artifact@v4
with:
name: wheels-windows-latest
- path: wheels-windows
+ path: dist
- - name: Publish dist
+ - name: Build source archive
run: |
python -m build . -s
- tree dist wheels-ubuntu wheels-macos wheels-windows
- twine upload dist/* wheels-ubuntu/*.whl wheels-macos/*.whl wheels-windows/*.whl
- env:
- TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
- TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
+ tree dist
+
+ - name: Publish to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ print-hash: true
+
- uses: marvinpinto/action-automatic-releases@latest
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
title: ${{ steps.get_tag.outputs.TAG }}
files: |
- wheels-ubuntu/*.whl
- wheels-macos/*.whl
- wheels-windows/*.whl
dist/*
docs:
@@ -156,12 +199,10 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
- python-version: '3.12'
+ python-version: '3.14'
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip setuptools wheel build
- make build
+ - name: Install uv
+ uses: astral-sh/setup-uv@v4
- name: Build docs
run: make docs
diff --git a/.gitignore b/.gitignore
index 0033cf3..3b9b434 100644
--- a/.gitignore
+++ b/.gitignore
@@ -81,7 +81,7 @@ celerybeat-schedule
.env
# virtualenv
-.venv/
+.venv*/
venv/
ENV/
@@ -113,3 +113,10 @@ deploy_key*
cython_debug
temp
+bin
+include
+instances.enabled
+modules
+templates
+uv.lock
+.DS_Store
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7493646..2765cc4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## v2.5.0
+**Breaking changes**
+* Dropped support for Python 3.7 and 3.8 (minimum required version is now 3.9)
+
+**New features**
+* Added support for Python 3.14, including free-threaded (no-GIL) builds
+* Added support for PyPy 3.11
+
+**Other changes**
+* Upgraded Cython to 3.2.4
+* Declared Cython module as `freethreading_compatible` for Python 3.13+
+* Disabled C freelist in free-threaded builds to ensure thread safety
+
## v2.4.0
**New features**
* Added support for Python 3.13 [#37](https://github.com/igorcoding/asynctnt/issues/37)
diff --git a/Makefile b/Makefile
index 90e8798..6cc9c93 100644
--- a/Makefile
+++ b/Makefile
@@ -1,67 +1,62 @@
.PHONY: clean build local debug annotate dist docs style mypy ruff style-check lint test quicktest coverage
-PYTHON?=python
-
all: local
-clean:
- pip uninstall -y asynctnt
- rm -rf asynctnt/*.c asynctnt/*.h asynctnt/*.cpp
- rm -rf asynctnt/*.so asynctnt/*.html
- rm -rf asynctnt/iproto/*.c asynctnt/iproto/*.h
- rm -rf asynctnt/iproto/*.so asynctnt/iproto/*.html asynctnt/iproto/requests/*.html
- rm -rf build *.egg-info .eggs dist
- find . -name '__pycache__' | xargs rm -rf
- rm -rf htmlcov
- rm -rf __tnt*
- rm -rf tests/__tnt*
-
-
build:
- $(PYTHON) -m pip install -e '.[test,docs]'
+ uv pip install -e '.[test,docs]'
local:
- $(PYTHON) -m pip install -e .
-
+ uv pip install -e .
debug: clean
- ASYNCTNT_DEBUG=1 $(PYTHON) -m pip install -e '.[test]'
-
+ ASYNCTNT_DEBUG=1 uv pip install -e '.[test]'
annotate:
cython -3 -a asynctnt/iproto/protocol.pyx
-dist:
- $(PYTHON) -m build .
-
-docs: build
- $(MAKE) -C docs html
+lint: style-check ruff
style:
- $(PYTHON) -m black .
- $(PYTHON) -m isort .
+ uv run --active ruff format .
+ uv run --active ruff check --select I,F401 --fix .
-mypy:
- $(PYTHON) -m mypy --enable-error-code ignore-without-code .
+style-check:
+ uv run --active ruff format --check .
ruff:
- $(PYTHON) -m ruff check .
-
-style-check:
- $(PYTHON) -m black --check --diff .
- $(PYTHON) -m isort --check --diff .
+ uv run --active ruff check .
-lint: style-check ruff
+mypy:
+ uv run --active mypy --enable-error-code ignore-without-code .
-test: lint
- PYTHONASYNCIODEBUG=1 $(PYTHON) -m pytest
- $(PYTHON) -m pytest
- USE_UVLOOP=1 $(PYTHON) -m pytest
+test:
+ PYTHONASYNCIODEBUG=1 uv run --active pytest
+ uv run --active pytest
+ USE_UVLOOP=1 uv run --active pytest
quicktest:
- $(PYTHON) -m pytest
+ uv run --active pytest
coverage:
- $(PYTHON) -m pytest --cov
- ./scripts/run_until_success.sh $(PYTHON) -m coverage report -m
- ./scripts/run_until_success.sh $(PYTHON) -m coverage html
+ uv run --active pytest --cov
+ ./scripts/run_until_success.sh uv run --active coverage report -m
+ ./scripts/run_until_success.sh uv run --active coverage html
+
+dist:
+ uv pip install build
+ uv run --active python -m build .
+
+docs: build
+ $(MAKE) -C docs html
+
+clean:
+ uv pip uninstall asynctnt
+ rm -rf asynctnt/*.c asynctnt/*.h asynctnt/*.cpp
+ rm -rf asynctnt/*.so asynctnt/*.html
+ rm -rf asynctnt/iproto/*.c asynctnt/iproto/*.h
+ rm -rf asynctnt/iproto/*.so asynctnt/iproto/*.html asynctnt/iproto/requests/*.html
+ rm -rf build *.egg-info .eggs dist
+ find . -name '__pycache__' | xargs rm -rf
+ rm -rf htmlcov
+ rm -rf __tnt*
+ rm -rf tests/__tnt*
diff --git a/README.md b/README.md
index 1f4a24b..dec8044 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[](https://github.com/igorcoding/asynctnt/actions)
[](https://pypi.python.org/pypi/asynctnt)
[](https://codeclimate.com/github/igorcoding/asynctnt/maintainability)
-
+
@@ -11,7 +11,7 @@ asynctnt is a high-performance [Tarantool](https://tarantool.org/) database
connector library for Python/asyncio. It was highly inspired by
[asyncpg](https://github.com/MagicStack/asyncpg) module.
-asynctnt requires Python 3.7 or later and is supported for Tarantool
+asynctnt requires Python 3.9 or later and is supported for Tarantool
versions 1.10+.
diff --git a/asynctnt/__init__.py b/asynctnt/__init__.py
index 27e6b8a..3bcfa95 100644
--- a/asynctnt/__init__.py
+++ b/asynctnt/__init__.py
@@ -18,4 +18,4 @@
TarantoolTuple,
)
-__version__ = "2.4.0"
+__version__ = "2.5.0"
diff --git a/asynctnt/connection.py b/asynctnt/connection.py
index 4fd8e3c..0dea13b 100644
--- a/asynctnt/connection.py
+++ b/asynctnt/connection.py
@@ -134,7 +134,7 @@ def __init__(
self._auto_refetch_schema = True
if not self._fetch_schema:
logger.warning(
- "Setting fetch_schema to True as " "auto_refetch_schema is True"
+ "Setting fetch_schema to True as auto_refetch_schema is True"
)
self._fetch_schema = True
else:
@@ -257,12 +257,12 @@ async def full_connect():
if self._host.startswith("unix/"):
unix_path = self._port
assert isinstance(unix_path, str), (
- "port must be a str instance for " "unix socket"
+ "port must be a str instance for unix socket"
)
assert unix_path, "No unix file path specified"
- assert os.path.exists(
- unix_path
- ), "Unix socket `{}` not found".format(unix_path)
+ assert os.path.exists(unix_path), (
+ "Unix socket `{}` not found".format(unix_path)
+ )
conn = loop.create_unix_connection(
functools.partial(
diff --git a/asynctnt/iproto/ext/datetime.pyx b/asynctnt/iproto/ext/datetime.pyx
index 0694490..667677a 100644
--- a/asynctnt/iproto/ext/datetime.pyx
+++ b/asynctnt/iproto/ext/datetime.pyx
@@ -1,5 +1,10 @@
-cimport cpython.datetime
-from cpython.datetime cimport PyDateTimeAPI, datetime, datetime_tzinfo, timedelta_new
+from cpython.datetime cimport (
+ datetime,
+ datetime_from_timestamp,
+ datetime_tzinfo,
+ timedelta_new,
+ timezone_new,
+)
from libc.stdint cimport uint32_t
from libc.string cimport memcpy
@@ -80,8 +85,4 @@ cdef object datetime_to_py(IProtoDateTime *dt):
tz = timezone_new(delta)
timestamp = dt.seconds + ( dt.nsec) / 1e9
- return PyDateTimeAPI.DateTime_FromTimestamp(
- PyDateTimeAPI.DateTimeType,
- (timestamp,) if tz is None else (timestamp, tz),
- NULL,
- )
+ return datetime_from_timestamp(timestamp, tz)
diff --git a/asynctnt/iproto/protocol.pxd b/asynctnt/iproto/protocol.pxd
index f7e5045..e5e350d 100644
--- a/asynctnt/iproto/protocol.pxd
+++ b/asynctnt/iproto/protocol.pxd
@@ -5,7 +5,6 @@ include "const.pxi"
include "cmsgpuck.pxd"
include "xd.pxd"
-include "python.pxd"
include "bit.pxd"
include "unicodeutil.pxd"
diff --git a/asynctnt/iproto/protocol.pyx b/asynctnt/iproto/protocol.pyx
index 0e8601c..4e4a059 100644
--- a/asynctnt/iproto/protocol.pyx
+++ b/asynctnt/iproto/protocol.pyx
@@ -1,4 +1,5 @@
# cython: language_level=3
+# cython: freethreading_compatible=True
cimport cpython.dict
from cpython.datetime cimport import_datetime
diff --git a/asynctnt/iproto/python.pxd b/asynctnt/iproto/python.pxd
deleted file mode 100644
index 8446aaa..0000000
--- a/asynctnt/iproto/python.pxd
+++ /dev/null
@@ -1,77 +0,0 @@
-from cpython.version cimport PY_VERSION_HEX
-
-
-cdef extern from "Python.h":
- char *PyByteArray_AS_STRING(object obj)
- int Py_REFCNT(object obj)
-
-
-cdef extern from "datetime.h":
- """
- /* Backport for Python 2.x */
- #if PY_MAJOR_VERSION < 3
- #ifndef PyDateTime_DELTA_GET_DAYS
- #define PyDateTime_DELTA_GET_DAYS(o) (((PyDateTime_Delta*)o)->days)
- #endif
- #ifndef PyDateTime_DELTA_GET_SECONDS
- #define PyDateTime_DELTA_GET_SECONDS(o) (((PyDateTime_Delta*)o)->seconds)
- #endif
- #ifndef PyDateTime_DELTA_GET_MICROSECONDS
- #define PyDateTime_DELTA_GET_MICROSECONDS(o) (((PyDateTime_Delta*)o)->microseconds)
- #endif
- #endif
- /* Backport for Python < 3.6 */
- #if PY_VERSION_HEX < 0x030600a4
- #ifndef PyDateTime_TIME_GET_FOLD
- #define PyDateTime_TIME_GET_FOLD(o) ((void)(o), 0)
- #endif
- #ifndef PyDateTime_DATE_GET_FOLD
- #define PyDateTime_DATE_GET_FOLD(o) ((void)(o), 0)
- #endif
- #endif
- /* Backport for Python < 3.6 */
- #if PY_VERSION_HEX < 0x030600a4
- #define __Pyx_DateTime_DateTimeWithFold(year, month, day, hour, minute, second, microsecond, tz, fold) \
- ((void)(fold), PyDateTimeAPI->DateTime_FromDateAndTime(year, month, day, hour, minute, second, \
- microsecond, tz, PyDateTimeAPI->DateTimeType))
- #define __Pyx_DateTime_TimeWithFold(hour, minute, second, microsecond, tz, fold) \
- ((void)(fold), PyDateTimeAPI->Time_FromTime(hour, minute, second, microsecond, tz, PyDateTimeAPI->TimeType))
- #else /* For Python 3.6+ so that we can pass tz */
- #define __Pyx_DateTime_DateTimeWithFold(year, month, day, hour, minute, second, microsecond, tz, fold) \
- PyDateTimeAPI->DateTime_FromDateAndTimeAndFold(year, month, day, hour, minute, second, \
- microsecond, tz, fold, PyDateTimeAPI->DateTimeType)
- #define __Pyx_DateTime_TimeWithFold(hour, minute, second, microsecond, tz, fold) \
- PyDateTimeAPI->Time_FromTimeAndFold(hour, minute, second, microsecond, tz, fold, PyDateTimeAPI->TimeType)
- #endif
- /* Backport for Python < 3.7 */
- #if PY_VERSION_HEX < 0x030700b1
- #define __Pyx_TimeZone_UTC NULL
- #define __Pyx_TimeZone_FromOffset(offset) ((void)(offset), (PyObject*)NULL)
- #define __Pyx_TimeZone_FromOffsetAndName(offset, name) ((void)(offset), (void)(name), (PyObject*)NULL)
- #else
- #define __Pyx_TimeZone_UTC PyDateTime_TimeZone_UTC
- #define __Pyx_TimeZone_FromOffset(offset) PyTimeZone_FromOffset(offset)
- #define __Pyx_TimeZone_FromOffsetAndName(offset, name) PyTimeZone_FromOffsetAndName(offset, name)
- #endif
- /* Backport for Python < 3.10 */
- #if PY_VERSION_HEX < 0x030a00a1
- #ifndef PyDateTime_TIME_GET_TZINFO
- #define PyDateTime_TIME_GET_TZINFO(o) \
- ((((PyDateTime_Time*)o)->hastzinfo) ? ((PyDateTime_Time*)o)->tzinfo : Py_None)
- #endif
- #ifndef PyDateTime_DATE_GET_TZINFO
- #define PyDateTime_DATE_GET_TZINFO(o) \
- ((((PyDateTime_DateTime*)o)->hastzinfo) ? ((PyDateTime_DateTime*)o)->tzinfo : Py_None)
- #endif
- #endif
- """
-
- # The above macros is Python 3.7+ so we use these instead
- object __Pyx_TimeZone_FromOffset(object offset)
-
-
-cdef inline object timezone_new(object offset):
- if PY_VERSION_HEX < 0x030700b1:
- from datetime import timezone
- return timezone(offset)
- return __Pyx_TimeZone_FromOffset(offset)
diff --git a/asynctnt/iproto/tupleobj/tupleobj.c b/asynctnt/iproto/tupleobj/tupleobj.c
index bb92690..4a85384 100644
--- a/asynctnt/iproto/tupleobj/tupleobj.c
+++ b/asynctnt/iproto/tupleobj/tupleobj.c
@@ -12,8 +12,10 @@
static PyObject *ttuple_iter(PyObject *);
static PyObject *ttuple_new_items_iter(PyObject *);
+#ifndef Py_GIL_DISABLED
static AtntTupleObject *free_list[AtntTuple_MAXSAVESIZE];
static int numfree[AtntTuple_MAXSAVESIZE];
+#endif
PyObject *
@@ -31,12 +33,15 @@ AtntTuple_New(PyObject *metadata, Py_ssize_t size)
return NULL;
}
+#ifndef Py_GIL_DISABLED
if (size < AtntTuple_MAXSAVESIZE && (o = free_list[size]) != NULL) {
free_list[size] = (AtntTupleObject *) o->ob_item[0];
numfree[size]--;
_Py_NewReference((PyObject *)o);
}
- else {
+ else
+#endif
+ {
/* Check for overflow */
if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - sizeof(AtntTupleObject) -
sizeof(PyObject *)) / sizeof(PyObject *)) {
@@ -79,6 +84,7 @@ ttuple_dealloc(AtntTupleObject *o)
Py_CLEAR(o->ob_item[i]);
}
+#ifndef Py_GIL_DISABLED
if (len < AtntTuple_MAXSAVESIZE &&
numfree[len] < AtntTuple_MAXFREELIST &&
AtntTuple_CheckExact(o))
@@ -88,10 +94,11 @@ ttuple_dealloc(AtntTupleObject *o)
free_list[len] = o;
goto done; /* return */
}
+#endif
}
Py_TYPE(o)->tp_free((PyObject *)o);
done:
- CPy_TRASHCAN_END(o)
+ CPy_TRASHCAN_END(o);
}
@@ -133,7 +140,7 @@ ttuple_hash(AtntTupleObject *v)
}
len = Py_SIZE(v);
- mult = _PyHASH_MULTIPLIER;
+ mult = ATNT_HASH_MULTIPLIER;
x = 0x345678UL;
p = v->ob_item;
@@ -404,12 +411,13 @@ ttuple_repr(AtntTupleObject *v)
}
#else
- _PyUnicodeWriter writer;
- _PyUnicodeWriter_Init(&writer);
- writer.overallocate = 1;
- writer.min_length = 12; /* */
+ ATNT_UW_DECL(writer);
+ ATNT_UW_CREATE(writer, 12); /* */
+ if (ATNT_UW_CREATE_FAILED(writer)) {
+ goto error;
+ }
- if (_PyUnicodeWriter_WriteASCIIString(&writer, " 0) {
- if (_PyUnicodeWriter_WriteChar(&writer, ' ') < 0) {
+ if (ATNT_UW_WRITE_CHAR(ATNT_UW_REF(writer), ' ') < 0) {
Py_DECREF(key_repr);
Py_DECREF(val_repr);
goto error;
}
}
- if (_PyUnicodeWriter_WriteStr(&writer, key_repr) < 0) {
+ if (ATNT_UW_WRITE_STR(ATNT_UW_REF(writer), key_repr) < 0) {
Py_DECREF(key_repr);
Py_DECREF(val_repr);
goto error;
}
Py_DECREF(key_repr);
- if (_PyUnicodeWriter_WriteChar(&writer, '=') < 0) {
+ if (ATNT_UW_WRITE_CHAR(ATNT_UW_REF(writer), '=') < 0) {
Py_DECREF(val_repr);
goto error;
}
- if (_PyUnicodeWriter_WriteStr(&writer, val_repr) < 0) {
+ if (ATNT_UW_WRITE_STR(ATNT_UW_REF(writer), val_repr) < 0) {
Py_DECREF(val_repr);
goto error;
}
@@ -514,17 +522,16 @@ ttuple_repr(AtntTupleObject *v)
}
Py_XDECREF(parts_joined);
#else
- writer.overallocate = 0;
if (oversize) {
- if (_PyUnicodeWriter_WriteASCIIString(&writer, " ...>", 5) < 0) {
+ if (ATNT_UW_WRITE_ASCII(ATNT_UW_REF(writer), " ...>", 5) < 0) {
goto error;
}
} else {
- if (_PyUnicodeWriter_WriteChar(&writer, '>') < 0) {
+ if (ATNT_UW_WRITE_CHAR(ATNT_UW_REF(writer), '>') < 0) {
goto error;
}
}
- result = _PyUnicodeWriter_Finish(&writer);
+ result = ATNT_UW_FINISH(ATNT_UW_REF(writer));
#endif
Py_XDECREF(keys_iter);
@@ -536,7 +543,7 @@ ttuple_repr(AtntTupleObject *v)
#if defined(PYPY_VERSION)
Py_XDECREF(parts);
#else
- _PyUnicodeWriter_Dealloc(&writer);
+ ATNT_UW_DISCARD(ATNT_UW_REF(writer));
#endif
Py_ReprLeave((PyObject *)v);
return NULL;
diff --git a/asynctnt/iproto/tupleobj/tupleobj.h b/asynctnt/iproto/tupleobj/tupleobj.h
index cf26667..5435ac7 100644
--- a/asynctnt/iproto/tupleobj/tupleobj.h
+++ b/asynctnt/iproto/tupleobj/tupleobj.h
@@ -12,15 +12,46 @@ extern "C" {
# define CPy_TRASHCAN_BEGIN(op, dealloc) do {} while(0);
# define CPy_TRASHCAN_END(op) do {} while(0);
#else
-
-#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 8
# define CPy_TRASHCAN_BEGIN(op, dealloc) Py_TRASHCAN_BEGIN(op, dealloc)
# define CPy_TRASHCAN_END(op) Py_TRASHCAN_END
-#else
-# define CPy_TRASHCAN_BEGIN(op, dealloc) Py_TRASHCAN_SAFE_BEGIN(op)
-# define CPy_TRASHCAN_END(op) Py_TRASHCAN_SAFE_END(op)
+
+/*
+ * PyUnicodeWriter compatibility macros for Python 3.14+
+ * Python 3.14 introduced public PyUnicodeWriter API, deprecating private _PyUnicodeWriter.
+ * These macros are only defined for CPython (not PyPy).
+ */
+#if PY_VERSION_HEX >= 0x030E0000 /* Python 3.14+ */
+# define ATNT_UW_DECL(name) PyUnicodeWriter *name
+# define ATNT_UW_CREATE(name, len) name = PyUnicodeWriter_Create(len)
+# define ATNT_UW_CREATE_FAILED(name) (name == NULL)
+# define ATNT_UW_REF(name) name
+# define ATNT_UW_WRITE_ASCII PyUnicodeWriter_WriteASCII
+# define ATNT_UW_WRITE_CHAR PyUnicodeWriter_WriteChar
+# define ATNT_UW_WRITE_STR PyUnicodeWriter_WriteStr
+# define ATNT_UW_FINISH PyUnicodeWriter_Finish
+# define ATNT_UW_DISCARD PyUnicodeWriter_Discard
+#else /* Python < 3.14 */
+# define ATNT_UW_DECL(name) _PyUnicodeWriter name
+# define ATNT_UW_CREATE(name, len) do { _PyUnicodeWriter_Init(&name); name.overallocate = 1; name.min_length = len; } while(0)
+# define ATNT_UW_CREATE_FAILED(name) (0) /* _PyUnicodeWriter_Init doesn't fail */
+# define ATNT_UW_REF(name) &name
+# define ATNT_UW_WRITE_ASCII _PyUnicodeWriter_WriteASCIIString
+# define ATNT_UW_WRITE_CHAR _PyUnicodeWriter_WriteChar
+# define ATNT_UW_WRITE_STR _PyUnicodeWriter_WriteStr
+# define ATNT_UW_FINISH _PyUnicodeWriter_Finish
+# define ATNT_UW_DISCARD _PyUnicodeWriter_Dealloc
#endif
+#endif /* !PYPY_VERSION */
+
+/*
+ * PyHASH_MULTIPLIER compatibility macro for Python 3.13+
+ * Python 3.13 introduced public PyHASH_MULTIPLIER, replacing private _PyHASH_MULTIPLIER.
+ */
+#if PY_VERSION_HEX >= 0x030D0000 /* Python 3.13+ */
+# define ATNT_HASH_MULTIPLIER PyHASH_MULTIPLIER
+#else
+# define ATNT_HASH_MULTIPLIER _PyHASH_MULTIPLIER
#endif
/* Largest ttuple to save on free list */
diff --git a/bench/benchmark.py b/bench/benchmark.py
index 1346ac2..2125a0b 100644
--- a/bench/benchmark.py
+++ b/bench/benchmark.py
@@ -20,7 +20,7 @@ def main():
parser.add_argument("-b", type=int, default=300, help="number of bulks")
args = parser.parse_args()
- print("Running {} requests in {} batches. ".format(args.n, args.b))
+ print("Running {} requests in {} batches. ".format(args.n, args.b)) # noqa: T201
scenarios = [
["ping", []],
@@ -34,30 +34,27 @@ def main():
]
for use_uvloop in [True]:
+ run_func = asyncio.run
if use_uvloop:
try:
import uvloop
except ImportError:
- print("No uvloop installed. Skipping.")
+ print("No uvloop installed. Skipping.") # noqa: T201
continue
- asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
- else:
- asyncio.set_event_loop_policy(None)
- asyncio.set_event_loop(None)
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
+ run_func = uvloop.run
- print("--------- uvloop: {} --------- ".format(use_uvloop))
+ print("--------- uvloop: {} --------- ".format(use_uvloop)) # noqa: T201
for name, conn_creator in [
("asynctnt", create_asynctnt),
# ('aiotarantool', create_aiotarantool),
]:
- conn = loop.run_until_complete(conn_creator())
- for scenario in scenarios:
- loop.run_until_complete(
- async_bench(
+
+ async def main(name=name, conn_creator=conn_creator):
+ conn = await conn_creator()
+ for scenario in scenarios:
+ await async_bench(
name,
conn,
args.n,
@@ -66,7 +63,8 @@ def main():
args=scenario[1],
kwargs=scenario[2] if len(scenario) > 2 else {},
)
- )
+
+ run_func(main())
async def async_bench(name, conn, n, b, method, args=None, kwargs=None):
@@ -87,7 +85,7 @@ async def bulk_f():
end = datetime.datetime.now()
elapsed = end - start
- print(
+ print( # noqa: T201
"{} [{}] Elapsed: {}, RPS: {}".format(
name, method, elapsed, n / elapsed.total_seconds()
)
diff --git a/pyproject.toml b/pyproject.toml
index 0a91beb..e74c413 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ description = "A fast Tarantool Database connector for Python/asyncio."
authors = [
{ name = "igorcoding", email = "igorcoding@gmail.com" }
]
-license = {text = "Apache License, Version 2.0"}
+license-files = ["LICENSE.txt"]
dynamic = ["version"]
classifiers=[
"Development Status :: 5 - Production/Stable",
@@ -14,19 +14,18 @@ classifiers=[
"Operating System :: Microsoft :: Windows",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
'Programming Language :: Python :: Implementation :: CPython',
"Intended Audience :: Developers",
- "License :: OSI Approved :: Apache Software License",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Database :: Front-Ends"
]
-requires-python = '>=3.7.0'
+requires-python = '>=3.9.0'
readme = "README.md"
dependencies = [
'PyYAML >= 5.0',
@@ -37,16 +36,13 @@ github = "https://github.com/igorcoding/asynctnt"
[project.optional-dependencies]
test = [
- 'isort',
- 'black',
'ruff',
'uvloop>=0.12.3; platform_system != "Windows" and platform.python_implementation != "PyPy"',
'pytest',
'pytest-cov',
'coverage[toml]',
'pytz',
- 'python-dateutil',
- "Cython==3.0.11", # for coverage
+ "Cython==3.2.4", # for coverage
]
docs = [
@@ -60,10 +56,9 @@ docs = [
[build-system]
requires = [
- "setuptools>=60",
+ "setuptools>=77",
"wheel",
-
- "Cython==3.0.11",
+ "Cython==3.2.4",
]
build-backend = "setuptools.build_meta"
@@ -107,33 +102,66 @@ show_missing = true
[tool.coverage.html]
directory = "htmlcov"
-[tool.black]
-extend-exclude = '(env|.env|venv|.venv).*'
-
-[tool.isort]
-profile = "black"
-multi_line_output = 3
-skip_glob = [
- "env*",
- "venv*",
-]
-
-
[tool.ruff]
-lint.select = [
- "E", # pycodestyle errors
- "W", # pycodestyle warnings
- "F", # pyflakes
- # "I", # isort
- "C", # flake8-comprehensions
- "B", # flake8-bugbear
+extend-exclude = [".venv*", "env*", "venv*"]
+
+[tool.ruff.format]
+# Аналогично black, двойные кавычки
+quote-style = "double"
+
+# Аналогично black, пробелы вместо табов
+indent-style = "space"
+
+# Аналогично black, уважаем trailing commas
+skip-magic-trailing-comma = false
+
+# Аналогично black, автоматически определяем подходящее окончание строки.
+line-ending = "auto"
+
+[tool.ruff.lint]
+# Список кодов или префиксов правил, которые следует считать исправляемыми. (https://docs.astral.sh/ruff/settings/#fixable)
+# По умолчанию все правила считаются исправляемыми.
+fixable = ["I", "RUF022", "RUF023", "F401"]
+preview = true
+
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # pyflakes
+ "I", # isort
+ "C", # flake8-comprehensions
+ "B", # flake8-bugbear
+ "T20", # flake8-print
]
-lint.ignore = [
- "E501", # line too long, handled by black
- "B008", # do not perform function calls in argument defaults
- "C901", # too complex
+ignore = [
+ "E501", # line too long, handled by black
+ "B008", # do not perform function calls in argument defaults
+ "C901", # too complex
]
-extend-exclude = [
- "app/store/migrations",
+[tool.ruff.lint.isort]
+# Позволяет использовать as в комбинации с группировкой (https://docs.astral.sh/ruff/settings/#isort-combine-as-imports)
+#from package import (
+# func1 as foo,
+# func2 as boo,
+#)
+combine-as-imports = true
+
+# Воспринимать следующие пакеты в качестве stdlib (https://docs.astral.sh/ruff/settings/#isort-extra-standard-library)
+extra-standard-library = ["typing_extensions"]
+
+section-order = [
+ "future",
+ "standard-library",
+ "third-party",
+ "first-party",
+ "local-folder"
]
+
+# Не добавлять пустую строку перед данными секциям (https://docs.astral.sh/ruff/settings/#isort-no-lines-before)
+no-lines-before = []
+
+[tool.ruff.lint.pep8-naming]
+# если навесить данные декораторы, то можно использовать cls (https://docs.astral.sh/ruff/settings/#pep8-naming-classmethod-decorators)
+# в качестве первого аргумента.
+classmethod-decorators = ["cached_classproperty", "classproperty"]
diff --git a/setup.py b/setup.py
index 2c4244c..d343767 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ def find_version():
return re.match(r"""__version__\s*=\s*(['"])([^'"]+)\1""", line).group(2)
-CYTHON_VERSION = "3.0.11"
+CYTHON_VERSION = "3.2.4"
class build_ext(setuptools_build_ext.build_ext):
diff --git a/tests/_testbase.py b/tests/_testbase.py
index 66c6e1a..a12b056 100644
--- a/tests/_testbase.py
+++ b/tests/_testbase.py
@@ -172,7 +172,7 @@ def _make_instance(cls, **kwargs):
tarantool_docker_tag = os.getenv("TARANTOOL_DOCKER_VERSION")
in_docker = False
if tarantool_docker_tag:
- print(
+ print( # noqa: T201
"Running tarantool in docker: {}:{}".format(
tarantool_docker_image or "tarantool/tarantool",
tarantool_docker_tag,
@@ -269,6 +269,7 @@ async def tnt_reconnect(self, **kwargs):
await self.tnt_connect(**kwargs)
def assertResponseEqual(self, resp, target, *args):
+ self.assertEqual(len(resp), len(target))
tuples = []
for item in resp:
if isinstance(item, TarantoolTuple):
diff --git a/tests/test_common.py b/tests/test_common.py
index e57deb5..c91447e 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -55,7 +55,7 @@ async def test__schema_refetch_on_schema_change(self):
self.assertIn("new_space", self.conn.schema.spaces)
finally:
await self.conn.eval(
- "local s = box.space.new_space;" "if s ~= nil then s:drop(); end"
+ "local s = box.space.new_space;if s ~= nil then s:drop(); end"
)
async def test__schema_refetch_manual(self):
@@ -93,7 +93,7 @@ async def test__schema_no_fetch_and_refetch(self):
self.assertEqual(self.conn.schema_id, -1)
# Changing scheme
- await self.conn.eval("s = box.schema.create_space('new_space');" "s:drop();")
+ await self.conn.eval("s = box.schema.create_space('new_space');s:drop();")
try:
await self.conn.ping()
@@ -105,16 +105,14 @@ async def test__schema_no_fetch_and_refetch(self):
self.assertEqual(self.conn.schema_id, -1)
async def test__parse_numeric_map_keys(self):
- res = await self.conn.eval(
- """return {
+ res = await self.conn.eval("""return {
[1] = 1,
[2] = 2,
hello = 3,
world = 4,
[-3] = 5,
[4.5] = 6
- }"""
- )
+ }""")
d = {1: 1, 2: 2, "hello": 3, "world": 4, -3: 5, 4.5: 6}
@@ -185,7 +183,7 @@ async def test__schema_refetch_next_byte(self):
try:
for _ in range(251):
await self.conn.eval(
- "s = box.schema.create_space('new_space');" "s:drop();"
+ "s = box.schema.create_space('new_space');s:drop();"
)
except TarantoolDatabaseError as e:
self.fail(e)
@@ -221,8 +219,7 @@ async def func():
)
async with conn:
await conn.eval(
- "s = box.schema.create_space('spacex');"
- "s:create_index('primary');"
+ "s = box.schema.create_space('spacex');s:create_index('primary');"
)
except TarantoolDatabaseError as e:
self.fail(e)
diff --git a/tests/test_mp_ext.py b/tests/test_mp_ext.py
index bb28552..def3cdc 100644
--- a/tests/test_mp_ext.py
+++ b/tests/test_mp_ext.py
@@ -1,10 +1,8 @@
import datetime
-import sys
import uuid
from dataclasses import dataclass
from decimal import Decimal
-import dateutil.parser
import pytz
import asynctnt
@@ -117,11 +115,9 @@ class MpExtErrorTestCase(BaseTarantoolTestCase):
@ensure_version(min=(2, 4, 1))
async def test__ext_error(self):
try:
- await self.conn.eval(
- """
+ await self.conn.eval("""
box.schema.space.create('_space')
- """
- )
+ """)
except TarantoolDatabaseError as e:
self.assertIsNotNone(e.error)
self.assertGreater(len(e.error.trace), 0)
@@ -136,12 +132,10 @@ async def test__ext_error(self):
@ensure_version(min=(2, 4, 1))
async def test__ext_error_custom(self):
try:
- await self.conn.eval(
- """
+ await self.conn.eval("""
local e = box.error.new{code=5,reason='A',type='B'}
box.error(e)
- """
- )
+ """)
except TarantoolDatabaseError as e:
self.assertIsNotNone(e.error)
self.assertGreater(len(e.error.trace), 0)
@@ -157,12 +151,10 @@ async def test__ext_error_custom(self):
@ensure_version(min=(2, 10))
async def test__ext_error_custom_return(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local e = box.error.new{code=5,reason='A',type='B'}
return e
- """
- )
+ """)
e = resp[0]
self.assertIsInstance(e, IProtoError)
self.assertGreater(len(e.trace), 0)
@@ -178,82 +170,68 @@ async def test__ext_error_custom_return(self):
@ensure_version(min=(2, 10))
async def test__ext_error_custom_return_with_disabled_exterror(self):
- await self.conn.eval(
- """
+ await self.conn.eval("""
require('msgpack').cfg{encode_error_as_ext = false}
- """
- )
+ """)
try:
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local e = box.error.new{code=5,reason='A',type='B'}
return e
- """
- )
+ """)
e = resp[0]
self.assertIsInstance(e, str)
self.assertEqual("A", e)
finally:
- await self.conn.eval(
- """
+ await self.conn.eval("""
require('msgpack').cfg{encode_error_as_ext = true}
- """
- )
+ """)
class MpExtDatetimeTestCase(BaseTarantoolTestCase):
@ensure_version(min=(2, 10))
async def test__ext_datetime_read(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local date = require('datetime')
return date.parse('2000-01-01T02:00:00.23+0300')
- """
- )
+ """)
res = resp[0]
- dt = datetime_fromisoformat("2000-01-01T02:00:00.230000+03:00")
+ dt = datetime.datetime.fromisoformat("2000-01-01T02:00:00.230000+03:00")
self.assertEqual(dt, res)
@ensure_version(min=(2, 10))
async def test__ext_datetime_tz(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local date = require('datetime')
return date.parse('2000-01-01T02:00:00 MSK')
- """
- )
+ """)
res = resp[0]
- dt = datetime_fromisoformat("2000-01-01T02:00:00+03:00")
+ dt = datetime.datetime.fromisoformat("2000-01-01T02:00:00+03:00")
self.assertEqual(dt, res)
@ensure_version(min=(2, 10))
async def test__ext_datetime_read_neg_tz(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local date = require('datetime')
return date.parse('2000-01-01T02:17:43.23-08:00')
- """
- )
+ """)
res = resp[0]
- dt = datetime_fromisoformat("2000-01-01T02:17:43.230000-08:00")
+ dt = datetime.datetime.fromisoformat("2000-01-01T02:17:43.230000-08:00")
self.assertEqual(dt, res)
@ensure_version(min=(2, 10))
async def test__ext_datetime_read_before_1970(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local date = require('datetime')
return date.parse('1930-01-01T02:17:43.23-08:00')
- """
- )
+ """)
res = resp[0]
- dt = datetime_fromisoformat("1930-01-01T02:17:43.230000-08:00")
+ dt = datetime.datetime.fromisoformat("1930-01-01T02:17:43.230000-08:00")
self.assertEqual(dt, res)
@ensure_version(min=(2, 10))
async def test__ext_datetime_write(self):
sp = "tester_ext_datetime"
- dt = datetime_fromisoformat("2000-01-01T02:17:43.230000-08:00")
+ dt = datetime.datetime.fromisoformat("2000-01-01T02:17:43.230000-08:00")
resp = await self.conn.insert(sp, [1, dt])
res = resp[0]
self.assertEqual(dt, res["dt"])
@@ -261,7 +239,7 @@ async def test__ext_datetime_write(self):
@ensure_version(min=(2, 10))
async def test__ext_datetime_write_before_1970(self):
sp = "tester_ext_datetime"
- dt = datetime_fromisoformat("1004-01-01T02:17:43.230000+04:00")
+ dt = datetime.datetime.fromisoformat("1004-01-01T02:17:43.230000+04:00")
resp = await self.conn.insert(sp, [1, dt])
res = resp[0]
self.assertEqual(dt, res["dt"])
@@ -269,7 +247,7 @@ async def test__ext_datetime_write_before_1970(self):
@ensure_version(min=(2, 10))
async def test__ext_datetime_write_without_tz(self):
sp = "tester_ext_datetime"
- dt = datetime_fromisoformat("2022-04-23T02:17:43.450000")
+ dt = datetime.datetime.fromisoformat("2022-04-23T02:17:43.450000")
resp = await self.conn.insert(sp, [1, dt])
res = resp[0]
self.assertEqual(dt, res["dt"])
@@ -277,7 +255,7 @@ async def test__ext_datetime_write_without_tz(self):
@ensure_version(min=(2, 10))
async def test__ext_datetime_write_without_tz_integer(self):
sp = "tester_ext_datetime"
- dt = datetime_fromisoformat("2022-04-23T02:17:43")
+ dt = datetime.datetime.fromisoformat("2022-04-23T02:17:43")
resp = await self.conn.insert(sp, [1, dt])
res = resp[0]
self.assertEqual(dt, res["dt"])
@@ -285,7 +263,7 @@ async def test__ext_datetime_write_without_tz_integer(self):
@ensure_version(min=(2, 10))
async def test__ext_datetime_write_pytz(self):
sp = "tester_ext_datetime"
- dt = datetime_fromisoformat("2022-04-23T02:17:43")
+ dt = datetime.datetime.fromisoformat("2022-04-23T02:17:43")
dt = pytz.timezone("Europe/Amsterdam").localize(dt)
resp = await self.conn.insert(sp, [1, dt])
res = resp[0]
@@ -294,7 +272,7 @@ async def test__ext_datetime_write_pytz(self):
@ensure_version(min=(2, 10))
async def test__ext_datetime_write_pytz_america(self):
sp = "tester_ext_datetime"
- dt = datetime_fromisoformat("2022-04-23T02:17:43")
+ dt = datetime.datetime.fromisoformat("2022-04-23T02:17:43")
dt = pytz.timezone("America/New_York").localize(dt)
resp = await self.conn.insert(sp, [1, dt])
res = resp[0]
@@ -304,8 +282,7 @@ async def test__ext_datetime_write_pytz_america(self):
class MpExtIntervalTestCase(BaseTarantoolTestCase):
@ensure_version(min=(2, 10))
async def test__ext_interval_read(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local datetime = require('datetime')
return datetime.interval.new({
year=1,
@@ -317,8 +294,7 @@ async def test__ext_interval_read(self):
sec=7,
nsec=8,
})
- """
- )
+ """)
self.assertEqual(
asynctnt.MPInterval(
year=1,
@@ -335,8 +311,7 @@ async def test__ext_interval_read(self):
@ensure_version(min=(2, 10))
async def test__ext_interval_read_adjust_last(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local datetime = require('datetime')
return datetime.interval.new({
year=1,
@@ -349,8 +324,7 @@ async def test__ext_interval_read_adjust_last(self):
nsec=8,
adjust='last'
})
- """
- )
+ """)
self.assertEqual(
asynctnt.MPInterval(
year=1,
@@ -368,8 +342,7 @@ async def test__ext_interval_read_adjust_last(self):
@ensure_version(min=(2, 10))
async def test__ext_interval_read_adjust_excess(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local datetime = require('datetime')
return datetime.interval.new({
year=1,
@@ -382,8 +355,7 @@ async def test__ext_interval_read_adjust_excess(self):
nsec=8,
adjust='excess'
})
- """
- )
+ """)
self.assertEqual(
asynctnt.MPInterval(
year=1,
@@ -401,8 +373,7 @@ async def test__ext_interval_read_adjust_excess(self):
@ensure_version(min=(2, 10))
async def test__ext_interval_read_all_negative(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local datetime = require('datetime')
return datetime.interval.new({
year=-1,
@@ -415,8 +386,7 @@ async def test__ext_interval_read_all_negative(self):
nsec=-8,
adjust='excess'
})
- """
- )
+ """)
self.assertEqual(
asynctnt.MPInterval(
year=-1,
@@ -434,8 +404,7 @@ async def test__ext_interval_read_all_negative(self):
@ensure_version(min=(2, 10))
async def test__ext_interval_read_all_mixed(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local datetime = require('datetime')
return datetime.interval.new({
year=1,
@@ -448,8 +417,7 @@ async def test__ext_interval_read_all_mixed(self):
nsec=-8,
adjust='excess'
})
- """
- )
+ """)
self.assertEqual(
asynctnt.MPInterval(
year=1,
@@ -467,12 +435,10 @@ async def test__ext_interval_read_all_mixed(self):
@ensure_version(min=(2, 10))
async def test__ext_interval_read_zeros(self):
- resp = await self.conn.eval(
- """
+ resp = await self.conn.eval("""
local datetime = require('datetime')
return datetime.interval.new({})
- """
- )
+ """)
self.assertEqual(
asynctnt.MPInterval(),
resp[0],
@@ -564,9 +530,3 @@ async def test__ext_interval_send_with_zeros(self):
],
)
self.assertTrue(resp[0])
-
-
-def datetime_fromisoformat(s):
- if sys.version_info < (3, 7, 0):
- return dateutil.parser.isoparse(s)
- return datetime.datetime.fromisoformat(s)
diff --git a/tests/test_op_ping.py b/tests/test_op_ping.py
index c4516a9..dfff1fc 100644
--- a/tests/test_op_ping.py
+++ b/tests/test_op_ping.py
@@ -36,15 +36,11 @@ async def test__ping_connection_lost(self):
try:
os.kill(self.tnt.pid, 0)
- running = True
except Exception:
- running = False
+ pass
with self.assertRaises(TarantoolNotConnectedError):
- res = await self.conn.ping()
- print(res)
- print("running", running)
- print(os.system("ps aux | grep tarantool"))
+ await self.conn.ping()
self.tnt.start()
await self.sleep(1)
diff --git a/tests/test_op_push.py b/tests/test_op_push.py
index cce71c7..7c24a20 100644
--- a/tests/test_op_push.py
+++ b/tests/test_op_push.py
@@ -228,7 +228,7 @@ async def test__push_read_all_disconnect(self):
@ensure_version(min=(1, 10))
async def test__push_read_all_multiple_iterators(self):
fut = self.conn.eval(
- "box.session.push(1);" "box.session.push(2);" "box.session.push(3);",
+ "box.session.push(1);box.session.push(2);box.session.push(3);",
push_subscribe=True,
)
it1 = PushIterator(fut)
diff --git a/tests/test_op_select.py b/tests/test_op_select.py
index 8a6bebe..76a5d36 100644
--- a/tests/test_op_select.py
+++ b/tests/test_op_select.py
@@ -190,7 +190,9 @@ async def test__select_all_by_hash_index(self):
data = await self._fill_data(4, space="no_schema_space")
res = await self.conn.select("no_schema_space", index="primary_hash")
- self.assertResponseEqual(res, data, "Body ok")
+ self.assertResponseEqual(
+ sorted(res, key=lambda t: t[0]), sorted(data, key=lambda t: t[0]), "Body ok"
+ )
async def test__select_key_tuple(self):
try:
diff --git a/tests/test_op_sql_execute.py b/tests/test_op_sql_execute.py
index 574e7a6..bab0cd7 100644
--- a/tests/test_op_sql_execute.py
+++ b/tests/test_op_sql_execute.py
@@ -130,7 +130,7 @@ async def test__sql_insert_autoincrement_multiple(self):
@ensure_version(min=(2, 0))
async def test__sql_insert_multiple(self):
res = await self.conn.execute(
- "insert into sql_space (id, name) " "values (1, 'one'), (2, 'two')"
+ "insert into sql_space (id, name) values (1, 'one'), (2, 'two')"
)
self.assertEqual(2, res.rowcount, "rowcount ok")
diff --git a/tests/test_response.py b/tests/test_response.py
index 5575aa9..0e781b5 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -183,7 +183,7 @@ async def test__response_repr(self):
)
self.assertEqual(
- "",
+ "",
repr(res[0]),
"repr ok",
)