├── .circleci └── config.yml ├── .gitignore ├── ChangeLog.md ├── LICENSE ├── README.rst ├── pyproject.toml ├── requirements.txt ├── setup.py └── src ├── sqlalchemy_rqlite ├── __init__.py ├── constants.py └── pyrqlite.py └── test └── test_register.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. 2 | # See: https://circleci.com/docs/configuration-reference 3 | version: 2.1 4 | 5 | # Define a job to be invoked later in a workflow. 6 | # See: https://circleci.com/docs/configuration-reference/#jobs 7 | jobs: 8 | test: 9 | # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. 10 | # See: https://circleci.com/docs/configuration-reference/#executor-job 11 | docker: 12 | - image: cimg/python:3.6.10 13 | # Add steps to the job 14 | # See: https://circleci.com/docs/configuration-reference/#steps 15 | steps: 16 | - checkout 17 | - run: pip install pytest pytest-cov SQLAlchemy 18 | - run: 19 | name: Run tests 20 | environment: 21 | PYTHONPATH: src 22 | command: python setup.py test 23 | 24 | # Orchestrate jobs using workflows 25 | # See: https://circleci.com/docs/configuration-reference/#workflows 26 | workflows: 27 | test-workflow: 28 | jobs: 29 | - test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | __pycache__ 4 | *.egg-info 5 | /.cache 6 | /.coverage 7 | /htmlcov 8 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 2.0 (May 11 2023) 4 | - [PR #22](https://github.com/rqlite/sqlalchemy-rqlite/pull/22): Rename dbapi method to import_dbapi. 5 | - [PR #23](https://github.com/rqlite/sqlalchemy-rqlite/pull/23): Set supports_statement_cache = True. 6 | 7 | ## 1.2 (May 11 2023) 8 | - [PR #17](https://github.com/rqlite/sqlalchemy-rqlite/pull/17): Make compatible with SQLAlchemy 2.0. 9 | - [PR #12](https://github.com/rqlite/sqlalchemy-rqlite/pull/12): Add do_ping for sqlalchemy_rqlite dialect. 10 | 11 | ## 1.1 (Feb 28 2021) 12 | - [PR #3](https://github.com/rqlite/sqlalchemy-rqlite/pull/3): Make dependencies installable via pip. 13 | - [PR #7](https://github.com/rqlite/sqlalchemy-rqlite/pull/7): Basic auth support. 14 | 15 | ## 1.0 (May 1st 2016) 16 | First release. 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Zac Medico 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | rqlite dialect for SQLAlchemy 2 | ============================== 3 | .. image:: https://circleci.com/gh/rqlite/sqlalchemy-rqlite/tree/master.svg?style=svg 4 | :target: https://circleci.com/gh/rqlite/sqlalchemy-rqlite/?branch=master 5 | 6 | This is the rqlite dialect driver for SQLAlchemy. 7 | 8 | 9 | installation 10 | ------------ 11 | 12 | To install this dialect run:: 13 | 14 | $ pip install sqlalchemy_rqlite 15 | 16 | or from source:: 17 | 18 | $ pip install -r ./requirements.txt 19 | $ python ./setup.py install 20 | 21 | 22 | usage 23 | ----- 24 | 25 | To start using this dialect:: 26 | 27 | from sqlalchemy import create_engine 28 | engine = create_engine('rqlite+pyrqlite://localhost:4001/', echo=True) 29 | 30 | If you don't want to install this library (for example during development) add 31 | this folder to your PYTHONPATH and register this dialect with SQLAlchemy:: 32 | 33 | from sqlalchemy.dialects import registry 34 | registry.register("rqlite.pyrqlite", "sqlalchemy_rqlite.pyrqlite", "dialect") 35 | 36 | testing 37 | ------- 38 | 39 | you need to have pytest and pytest-cov installed:: 40 | 41 | $ pip install pytest pytest-cov 42 | 43 | Run the test suite:: 44 | 45 | $ python setup.py test 46 | 47 | 48 | 49 | more info 50 | --------- 51 | 52 | * http://www.sqlalchemy.org/ 53 | * https://github.com/rqlite/rqlite 54 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | "wheel", 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrqlite 2 | SQLAlchemy 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from os.path import isdir, islink, relpath, dirname 4 | import subprocess 5 | import sys 6 | from setuptools import ( 7 | Command, 8 | setup, 9 | find_packages, 10 | ) 11 | 12 | # Load constants.py without importing __init__.py, since pip 13 | # calls setup.py before sqlalchemy is installed. 14 | with open(os.path.join('src', 'sqlalchemy_rqlite', 'constants.py'), 'rt') as f: 15 | exec(f.read()) 16 | 17 | 18 | class PyTest(Command): 19 | user_options = [('match=', 'k', 'Run only tests that match the provided expressions')] 20 | 21 | def initialize_options(self): 22 | self.match = None 23 | 24 | def finalize_options(self): 25 | pass 26 | 27 | def run(self): 28 | testpath = 'src/test' 29 | buildlink = 'build/lib/test' 30 | 31 | if isdir(dirname(buildlink)): 32 | if islink(buildlink): 33 | os.unlink(buildlink) 34 | 35 | os.symlink(relpath(testpath, dirname(buildlink)), buildlink) 36 | testpath = buildlink 37 | 38 | try: 39 | os.environ['EPYTHON'] = 'python{}.{}'.format(sys.version_info.major, sys.version_info.minor) 40 | subprocess.check_call(['py.test', '-v', testpath, 41 | '--cov-report=html', '--cov-report=term'] + 42 | (['-k', self.match] if self.match else []) + 43 | ['--cov={}'.format(p) for p in find_packages(dirname(testpath), exclude=['test'])]) 44 | 45 | finally: 46 | if islink(buildlink): 47 | os.unlink(buildlink) 48 | 49 | 50 | class PyLint(Command): 51 | user_options = [('errorsonly', 'E', 'Check only errors with pylint'), 52 | ('format=', 'f', 'Change the output format')] 53 | 54 | def initialize_options(self): 55 | self.errorsonly = 0 56 | self.format = 'colorized' 57 | 58 | def finalize_options(self): 59 | pass 60 | 61 | def run(self): 62 | cli_options = ['-E'] if self.errorsonly else [] 63 | cli_options.append('--output-format={0}'.format(self.format)) 64 | errno = subprocess.call(['pylint'] + cli_options + [ 65 | "--msg-template='{C}:{msg_id}:{path}:{line:3d},{column}: {obj}: {msg} ({symbol})'"] + 66 | find_packages('src', exclude=['test']), cwd='./src') 67 | raise SystemExit(errno) 68 | 69 | setup_params = dict( 70 | name="sqlalchemy_rqlite", 71 | version=__version__, 72 | description="SQLAlchemy dialect for rqlite", 73 | author=__author__, 74 | author_email=__email__, 75 | maintainer=__author__, 76 | maintainer_email=__email__, 77 | install_requires=[ 78 | "SQLAlchemy", 79 | "pyrqlite", 80 | ], 81 | dependency_links=['https://github.com/rqlite/pyrqlite/tarball/master#egg=pyrqlite-2'], 82 | classifiers=[ 83 | 'Development Status :: 3 - Alpha', 84 | 'Environment :: Console', 85 | 'Intended Audience :: Developers', 86 | 'License :: OSI Approved :: MIT License', 87 | 'Programming Language :: Python', 88 | 'Programming Language :: Python :: 2', 89 | 'Programming Language :: Python :: 2.7', 90 | 'Programming Language :: Python :: 3', 91 | 'Programming Language :: Python :: 3.3', 92 | 'Programming Language :: Python :: 3.4', 93 | 'Programming Language :: Python :: 3.5', 94 | 'Programming Language :: Python :: Implementation :: CPython', 95 | 'Programming Language :: Python :: Implementation :: PyPy', 96 | 'Topic :: Database :: Front-Ends', 97 | ], 98 | keywords='rqlite SQLAlchemy', 99 | package_dir={'': 'src'}, 100 | packages=find_packages('src', exclude=['test']), 101 | include_package_data=True, 102 | platforms=['Posix'], 103 | cmdclass={'test': PyTest, 'lint': PyLint}, 104 | entry_points={ 105 | "sqlalchemy.dialects": 106 | ["rqlite.pyrqlite = sqlalchemy_rqlite.pyrqlite:dialect"] 107 | }, 108 | license=__license__, 109 | ) 110 | 111 | if __name__ == '__main__': 112 | setup(**setup_params) 113 | -------------------------------------------------------------------------------- /src/sqlalchemy_rqlite/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from sqlalchemy.dialects.sqlite import base, pysqlite, pysqlcipher 3 | 4 | from sqlalchemy.dialects.sqlite.base import ( 5 | BLOB, BOOLEAN, CHAR, DATE, DATETIME, DECIMAL, FLOAT, INTEGER, REAL, 6 | NUMERIC, SMALLINT, TEXT, TIME, TIMESTAMP, VARCHAR, dialect, 7 | ) 8 | 9 | __all__ = ('BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 10 | 'FLOAT', 'INTEGER', 'NUMERIC', 'SMALLINT', 'TEXT', 'TIME', 11 | 'TIMESTAMP', 'VARCHAR', 'REAL', 'dialect') 12 | -------------------------------------------------------------------------------- /src/sqlalchemy_rqlite/constants.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = "2.0" 3 | __project__ = "sqlalchemy-rqlite" 4 | 5 | __author__ = "Zac Medico" 6 | __email__ = "zmedico@gmail.com" 7 | 8 | __copyright__ = "Copyright (C) 2016 Zac Medico" 9 | __license__ = "MIT" 10 | __description__ = "A SQLAlchemy dialect for rqlite" 11 | -------------------------------------------------------------------------------- /src/sqlalchemy_rqlite/pyrqlite.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from sqlalchemy.dialects.sqlite.base import SQLiteDialect, DATETIME, DATE 4 | from sqlalchemy import exc, pool 5 | from sqlalchemy import types as sqltypes 6 | from sqlalchemy import util 7 | 8 | import os 9 | 10 | 11 | class _SQLite_rqliteTimeStamp(DATETIME): 12 | def bind_processor(self, dialect): 13 | if dialect.native_datetime: 14 | return None 15 | else: 16 | return DATETIME.bind_processor(self, dialect) 17 | 18 | def result_processor(self, dialect, coltype): 19 | if dialect.native_datetime: 20 | return None 21 | else: 22 | return DATETIME.result_processor(self, dialect, coltype) 23 | 24 | 25 | class _SQLite_rqliteDate(DATE): 26 | def bind_processor(self, dialect): 27 | if dialect.native_datetime: 28 | return None 29 | else: 30 | return DATE.bind_processor(self, dialect) 31 | 32 | def result_processor(self, dialect, coltype): 33 | if dialect.native_datetime: 34 | return None 35 | else: 36 | return DATE.result_processor(self, dialect, coltype) 37 | 38 | 39 | class SQLiteDialect_rqlite(SQLiteDialect): 40 | default_paramstyle = 'qmark' 41 | supports_statement_cache = True 42 | 43 | colspecs = util.update_copy( 44 | SQLiteDialect.colspecs, 45 | { 46 | sqltypes.Date: _SQLite_rqliteDate, 47 | sqltypes.TIMESTAMP: _SQLite_rqliteTimeStamp, 48 | } 49 | ) 50 | 51 | if not getattr(util, "py2k", False): 52 | description_encoding = None 53 | 54 | driver = 'pyrqlite' 55 | 56 | # pylint: disable=method-hidden 57 | @classmethod 58 | def import_dbapi(cls): 59 | try: 60 | # pylint: disable=no-name-in-module 61 | from pyrqlite import dbapi2 as sqlite 62 | #from sqlite3 import dbapi2 as sqlite # try 2.5+ stdlib name. 63 | except ImportError: 64 | #raise e 65 | raise 66 | return sqlite 67 | 68 | # SQLAlchemy 1.x compatibility. 69 | dbapi = import_dbapi 70 | 71 | @classmethod 72 | def get_pool_class(cls, url): 73 | if url.database and url.database != ':memory:': 74 | return pool.NullPool 75 | else: 76 | return pool.SingletonThreadPool 77 | 78 | def create_connect_args(self, url): 79 | opts = dict(url.query.items()) 80 | util.coerce_kw_type(opts, 'connect_timeout', float) 81 | util.coerce_kw_type(opts, 'detect_types', int) 82 | util.coerce_kw_type(opts, 'max_redirects', int) 83 | opts['port'] = url.port 84 | opts['host'] = url.host 85 | 86 | if url.username: 87 | opts['user'] = url.username 88 | 89 | if url.password: 90 | opts['password'] = url.password 91 | 92 | return ([], opts) 93 | 94 | def is_disconnect(self, e, connection, cursor): 95 | return False 96 | 97 | def do_ping(self, dbapi_connection): 98 | try: 99 | dbapi_connection.ping(reconnect=True) 100 | except self.dbapi.Error as err: 101 | if self.is_disconnect(err, dbapi_connection, None): 102 | return False 103 | else: 104 | raise 105 | else: 106 | return True 107 | 108 | dialect = SQLiteDialect_rqlite 109 | -------------------------------------------------------------------------------- /src/test/test_register.py: -------------------------------------------------------------------------------- 1 | 2 | from sqlalchemy import ( 3 | create_engine, 4 | ) 5 | 6 | from sqlalchemy_rqlite import pyrqlite 7 | 8 | from sqlalchemy.dialects import registry 9 | 10 | def test_register(): 11 | registry.register("rqlite.pyrqlite", "sqlalchemy_rqlite.pyrqlite", "dialect") 12 | #engine = create_engine('rqlite+pyrqlite://localhost:4001/?detect_types=0&connect_timeout=3.0') 13 | #engine.dispose() 14 | --------------------------------------------------------------------------------