diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..60fa64f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,20 @@ +name: nameko-sqlalchemy CI build +on: [push] +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 2 + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: "${{ matrix.python-version }}" + - name: Upgrade pip + run: pip install pip setuptools wheel --upgrade + - name: Install tox + run: pip install tox tox-gh-actions + - name: Run tox + run: python -m tox diff --git a/.gitignore b/.gitignore index f518aea..538a43a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ .coverage *.pyc +.venv +.idea ## Packages *.egg-info dist -.tox +.tox \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a0d425e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,41 +0,0 @@ -language: python -python: 3.7 - -dist: xenial - -services: - - docker - -install: - - pip install tox - -matrix: - include: - - stage: test - python: 2.7 - env: TOX_ENV=py27 - - stage: test - python: 3.4 - env: TOX_ENV=py34 - - stage: test - python: 3.5 - env: TOX_ENV=py35 - - stage: test - python: 3.6 - env: TOX_ENV=py36 - - stage: test - python: 3.7 - env: TOX_ENV=py37 - - stage: deploy - script: skip - deploy: - provider: pypi - user: onefinestay - password: - secure: L838z+MtH79Px67frLMPo1LP/FULLDDlx6SOgZ/Npg+NBAs8D+Yu2OGCSLxgaHUU7FqK0Gqp4NNQxM8yLD1mGPbtgyd/DYlL8ugDHu45QEx+7YI2tAwc6XOKba1PXqYZi33/aCDUe5LYcVKfoNqXaXZxt25eWwI1VjnH5KJdEpI= - on: - tags: true - repo: nameko/nameko-sqlalchemy - -script: - - tox -e $TOX_ENV diff --git a/CHANGELOG.md b/CHANGELOG.md index ba57719..fd4d55c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ Release Notes ============= +Version 2.0.0 +------------- + +Released 2023-12-11 + +* Updated project to modern standards and reformatted code +* Dropped support for Python 2.7, 3.4, 3.5, 3.6, 3.7 +* Added support for Python 3.8, 3.9, 3.10 +* Switched from flake8 + pylint to isort + ruff + mypy +* Updated tox to 4+ +* Moved package config from setup.py to pyproject.toml +* Moved CI from travis to github actions + + Version 1.5.0 ------------- diff --git a/Makefile b/Makefile index e2f111d..9cce208 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,13 @@ -test: flake8 pylint pytest - -flake8: - flake8 nameko_sqlalchemy test setup.py - -pylint: - pylint nameko_sqlalchemy -E - -pytest: test-deps +test: test-deps coverage run --concurrency=eventlet --source nameko_sqlalchemy --branch -m \ pytest test \ - --test-db-url="mysql+pymysql://test_user:password@$(shell docker port nameko_sqlalchemy_test_mysql 3306)/nameko_sqlalchemy_test" \ - --toxiproxy-api-url=$(shell docker port nameko_sqlalchemy_test_toxiproxy 8474) \ - --toxiproxy-db-url="mysql+pymysql://test_user:password@$(shell docker port nameko_sqlalchemy_test_toxiproxy 3307)/nameko_sqlalchemy_test" + --test-db-url="mysql+pymysql://test_user:password@$(shell docker port nameko_sqlalchemy_test_mysql 3306 | grep -v '::')/nameko_sqlalchemy_test" \ + --toxiproxy-api-url=$(shell docker port nameko_sqlalchemy_test_toxiproxy 8474 | grep -v '::') \ + --toxiproxy-db-url="mysql+pymysql://test_user:password@$(shell docker port nameko_sqlalchemy_test_toxiproxy 3307 | grep -v '::')/nameko_sqlalchemy_test" coverage report --show-missing --fail-under=100 test-deps: container-cleanup mysql-setup toxiproxy-setup -start-containers: - @echo Starting docker containers... - toxiproxy-container: docker run --rm -d -p 8474 -p 3307 --name=nameko_sqlalchemy_test_toxiproxy shopify/toxiproxy @@ -27,7 +16,7 @@ mysql-container: toxiproxy-setup: toxiproxy-container @echo Setting up toxiproxy to mysql - docker exec -it nameko_sqlalchemy_test_toxiproxy /go/bin/toxiproxy-cli \ + docker exec nameko_sqlalchemy_test_toxiproxy /go/bin/toxiproxy-cli \ create nameko_sqlalchemy_test_mysql \ --listen=0.0.0.0:3307 \ --upstream=`docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nameko_sqlalchemy_test_mysql`:3306 diff --git a/nameko_sqlalchemy/__init__.py b/nameko_sqlalchemy/__init__.py index f428940..baf64ee 100644 --- a/nameko_sqlalchemy/__init__.py +++ b/nameko_sqlalchemy/__init__.py @@ -1,8 +1,8 @@ from nameko_sqlalchemy.database import ( # noqa: F401 - Database, - DB_URIS_KEY, DB_ENGINE_OPTIONS_KEY, DB_SESSION_OPTIONS_KEY, + DB_URIS_KEY, + Database, ) from nameko_sqlalchemy.database_session import DatabaseSession # noqa: F401 from nameko_sqlalchemy.transaction_retry import transaction_retry # noqa: F401 diff --git a/nameko_sqlalchemy/database.py b/nameko_sqlalchemy/database.py index 101ec5d..67b7b69 100644 --- a/nameko_sqlalchemy/database.py +++ b/nameko_sqlalchemy/database.py @@ -5,7 +5,6 @@ from sqlalchemy.orm import Session as BaseSession from sqlalchemy.orm import sessionmaker - DB_URIS_KEY = 'DB_URIS' DB_ENGINE_OPTIONS_KEY = 'DB_ENGINE_OPTIONS' DB_SESSION_OPTIONS_KEY = 'DB_SESSION_OPTIONS' diff --git a/nameko_sqlalchemy/database_session.py b/nameko_sqlalchemy/database_session.py index be892c5..66b0860 100644 --- a/nameko_sqlalchemy/database_session.py +++ b/nameko_sqlalchemy/database_session.py @@ -4,7 +4,7 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from nameko_sqlalchemy import DB_URIS_KEY +from nameko_sqlalchemy.database import DB_URIS_KEY class DatabaseSession(DependencyProvider): diff --git a/nameko_sqlalchemy/pytest_fixtures.py b/nameko_sqlalchemy/pytest_fixtures.py index 6306c17..00a2481 100644 --- a/nameko_sqlalchemy/pytest_fixtures.py +++ b/nameko_sqlalchemy/pytest_fixtures.py @@ -1,5 +1,4 @@ import pytest - from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker diff --git a/nameko_sqlalchemy/transaction_retry.py b/nameko_sqlalchemy/transaction_retry.py index e806e63..3b417e0 100644 --- a/nameko_sqlalchemy/transaction_retry.py +++ b/nameko_sqlalchemy/transaction_retry.py @@ -1,6 +1,6 @@ -from time import sleep import functools import operator +from time import sleep import wrapt from sqlalchemy import exc diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c66e6e5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,92 @@ +[build-system] +requires = ["setuptools>=68.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "nameko-sqlalchemy" +version = "2.0.0" +description = "SQLAlchemy dependency for nameko services" +license = {file = "LICENSE.txt"} +readme = "README.rst" +requires-python = ">=3.8" +authors = [{name="onefinestay", email="engineering@onefinestay.com"}] +classifiers = [ + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Internet", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + "nameko>=2.0.0", + "sqlalchemy>=1.4,<2" +] + +[project.urls] +Homepage = "https://github.com/onefinestay/nameko-sqlalchemy" + +[project.optional-dependencies] +dev = [ + "coverage==7.3.2", + "isort==5.12.0", + "mypy==1.7.1", + "pytest==7.4.3", + "requests==2.31.0", + "ruff==0.1.6", + "PyMySQL==1.1.0", + "types-mock==5.1.0.3", + "types-requests==2.31.0.10", +] + +[project.entry-points."pytest11"] +nameko_sqlalchemy = "nameko_sqlalchemy.pytest_fixtures" + +[tool.setuptools] +include-package-data = true +zip-safe = true + +[tool.isort] +profile = "black" +multi_line_output = 3 +src_paths = [ + "nameko_sqlalchemy/", + "test/", +] +known_first_party = "nameko_chassis" + +[tool.ruff] +extend-exclude = [ + ".venv", + "migrations", +] +ignore = [ + "E402", + "E501", +] +select = [ + "E", + "F", + "W", +] + +[tool.mypy] +python_version = "3.10" +plugins = "sqlalchemy.ext.mypy.plugin" +mypy_path = "nameko_sqlalchemy/" +namespace_packages = true +no_implicit_optional = true +no_implicit_reexport = true +strict_equality = true +warn_redundant_casts = true + +ignore_missing_imports = true + +[tool.pytest.ini_options] +norecursedirs = [".git", ".tox", "dist", "build"] +testpaths = ["test"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fd2cd20 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +nameko>=2.0.0 +sqlalchemy>=1.4,<2 \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 0fc09df..0000000 --- a/setup.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -from setuptools import setup - -setup( - name='nameko-sqlalchemy', - version='1.5.0', - description='SQLAlchemy dependency for nameko services', - author='onefinestay', - author_email='engineering@onefinestay.com', - url='http://github.com/onefinestay/nameko-sqlalchemy', - packages=['nameko_sqlalchemy'], - install_requires=[ - "nameko>=2.0.0", - "sqlalchemy" - ], - extras_require={ - 'dev': [ - "coverage==4.5.3", - "flake8==3.7.7", - "pylint>=1.9.4", # pinned for py27 support - "pytest==4.3.1", - "requests==2.21.0", - "PyMySQL", - ] - }, - entry_points={ - 'pytest11': [ - 'nameko_sqlalchemy=nameko_sqlalchemy.pytest_fixtures' - ] - }, - zip_safe=True, - license='Apache License, Version 2.0', - classifiers=[ - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Operating System :: MacOS :: MacOS X", - "Operating System :: POSIX", - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Topic :: Internet", - "Topic :: Software Development :: Libraries :: Python Modules", - - ] -) diff --git a/test/conftest.py b/test/conftest.py index 7b2ad88..cb147f7 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -2,8 +2,10 @@ # You should monkey patch the standard library as early as possible to avoid # importing anything before the patch is applied. # See http://eventlet.net/doc/patching.html#monkeypatching-the-standard-library -import eventlet import json + +import eventlet + eventlet.monkey_patch() # noqa (code before rest of imports) import pytest @@ -11,7 +13,6 @@ from sqlalchemy import Column, Integer, String from sqlalchemy.ext.declarative import declarative_base - TOXIPROXY_PROXY_NAME = 'nameko_sqlalchemy_test_mysql' @@ -32,9 +33,7 @@ def __init__(self, api_url): self.api_url = api_url def enable(self): - resource = 'http://{}/reset'.format( - self.api_url, TOXIPROXY_PROXY_NAME - ) + resource = 'http://{}/reset'.format(self.api_url) requests.post(resource) def disable(self): diff --git a/test/test_database.py b/test/test_database.py index 9426ce6..0fbf06f 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -4,15 +4,11 @@ from mock import Mock, patch from nameko.containers import ServiceContainer, WorkerContext from nameko.testing.services import dummy, entrypoint_hook -from nameko_sqlalchemy.database import ( - DB_URIS_KEY, - Database, - Session, -) from sqlalchemy import Column, String, create_engine from sqlalchemy.engine import Engine from sqlalchemy.ext.declarative import declarative_base +from nameko_sqlalchemy.database import DB_URIS_KEY, Database, Session DeclBase = declarative_base(name='examplebase') diff --git a/test/test_database_session.py b/test/test_database_session.py index 6dd296a..f8d0671 100644 --- a/test/test_database_session.py +++ b/test/test_database_session.py @@ -9,11 +9,8 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm.session import Session -from nameko_sqlalchemy import ( - DatabaseSession, - DB_URIS_KEY, -) - +from nameko_sqlalchemy.database import DB_URIS_KEY +from nameko_sqlalchemy.database_session import DatabaseSession DeclBase = declarative_base(name='examplebase') diff --git a/test/test_pytest_fixtures.py b/test/test_pytest_fixtures.py index 669b1c5..0fef5db 100644 --- a/test/test_pytest_fixtures.py +++ b/test/test_pytest_fixtures.py @@ -1,11 +1,9 @@ import pytest - from nameko.testing.services import dummy, worker_factory from sqlalchemy import Column, Integer, String from sqlalchemy.ext.declarative import declarative_base -from nameko_sqlalchemy import Database - +from nameko_sqlalchemy.database import Database pytest_plugins = "pytester" diff --git a/test/test_transaction_retry.py b/test/test_transaction_retry.py index 71ec061..7900140 100644 --- a/test/test_transaction_retry.py +++ b/test/test_transaction_retry.py @@ -1,21 +1,19 @@ import operator import sys +from test.conftest import DeclarativeBase, ExampleModel import pytest from mock import Mock from nameko.exceptions import ExtensionNotFound -from nameko.testing.services import entrypoint_hook -from nameko.testing.services import dummy +from nameko.testing.services import dummy, entrypoint_hook from sqlalchemy import create_engine -from sqlalchemy.exc import StatementError, OperationalError +from sqlalchemy.exc import OperationalError, StatementError from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker -from nameko_sqlalchemy import ( - Database, DatabaseSession, DB_URIS_KEY, transaction_retry -) -from test.conftest import DeclarativeBase, ExampleModel - +from nameko_sqlalchemy.database import DB_URIS_KEY, Database +from nameko_sqlalchemy.database_session import DatabaseSession +from nameko_sqlalchemy.transaction_retry import transaction_retry DeclBase = declarative_base(name='examplebase') @@ -104,14 +102,16 @@ def test_raises_error_if_cannot_reconnect( toxiproxy_db_session.add(ExampleModel(data='hello2')) toxiproxy_db_session.commit() - disconnect(reconnect=False) - @transaction_retry def get_model_count(): return toxiproxy_db_session.query(ExampleModel).count() - with pytest.raises(StatementError): - get_model_count() + try: + disconnect(reconnect=False) + with pytest.raises(StatementError): + get_model_count() + except Exception: + toxiproxy_db_session.rollback() def test_raises_if_connection_is_not_invalidated(): @@ -379,9 +379,8 @@ def test_retry_configuration(retry_kwargs, call_results, decorator = transaction_retry(**retry_kwargs) decorated = decorator(mocked_fcn) - if ( - isinstance(expected_result, type) and - issubclass(expected_result, Exception) + if isinstance(expected_result, type) and issubclass( + expected_result, Exception ): with pytest.raises(expected_result): decorated() diff --git a/test_requirements.txt b/test_requirements.txt new file mode 100644 index 0000000..09ae17f --- /dev/null +++ b/test_requirements.txt @@ -0,0 +1,11 @@ +-r requirements.txt +coverage==7.3.2 +isort==5.12.0 +mypy==1.7.1 +pytest==7.4.3 +requests==2.31.0 +PyMySQL==1.1.0 +ruff==0.1.6 +sqlalchemy2-stubs==0.0.2a37 +types-mock==5.1.0.3 +types-requests==2.31.0.10 \ No newline at end of file diff --git a/tox.ini b/tox.ini index e369f30..e186ccc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,41 @@ [tox] -envlist = {py27,py34,py35,py36,py37} +envlist = + clean, + check, + py{38,39,310}, + report skipsdist = True [testenv] -whitelist_externals = make - +deps = + -e{toxinidir}/ + -r{toxinidir}/test_requirements.txt +allowlist_externals = + make +commands = + make test usedevelop = true -extras = - dev -deps = - py27: pylint==1.9.4 - py{34,35,36,37}: pylint==2.3.1 +[testenv:check] +commands = + isort --verbose --check-only --diff nameko_sqlalchemy test + ruff nameko_sqlalchemy test + mypy nameko_sqlalchemy test +[testenv:report] +deps = coverage[toml] +skip_install = true commands = - make test + coverage report + coverage html + +[testenv:clean] +commands = coverage erase +skip_install = true +deps = coverage[toml] + +[gh-actions] +python = + 3.8: py38 + 3.9: py39 + 3.10: py310, clean, check, report