├── test-reports └── .gitkeep ├── tests └── unit │ ├── __init__.py │ └── test_nothing.py ├── .python-version ├── sqla_vertica_python ├── __init__.py └── vertica_python.py ├── metrics ├── bigfiles_high_water_mark ├── flake8_high_water_mark ├── punchlist_high_water_mark └── coverage_high_water_mark ├── requirements.txt ├── setup.cfg ├── publish.sh ├── .gitignore ├── deps.sh ├── Rakefile.quality ├── LICENSE ├── README.rst ├── Makefile ├── setup.py └── .circleci └── config.yml /test-reports/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.8.16 2 | -------------------------------------------------------------------------------- /sqla_vertica_python/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /metrics/bigfiles_high_water_mark: -------------------------------------------------------------------------------- 1 | 551 -------------------------------------------------------------------------------- /metrics/flake8_high_water_mark: -------------------------------------------------------------------------------- 1 | 33 2 | -------------------------------------------------------------------------------- /metrics/punchlist_high_water_mark: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /metrics/coverage_high_water_mark: -------------------------------------------------------------------------------- 1 | 30.1200 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | nose 2 | coverage 3 | PyYAML # used by Makefile for running quality gem 4 | sqlalchemy # needed by tests 5 | wheel # needed by deploy job in CircleCI 6 | twine # needed by deploy job in CircleCI 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pycodestyle] 2 | max_line_length=100 3 | 4 | [pep8] 5 | max_line_length=100 6 | 7 | [flake8] 8 | max_line_length=100 9 | max-complexity = 15 10 | 11 | 12 | [pylama:pycodestyle] 13 | max_line_length=100 14 | 15 | [pylama:flake8] 16 | max_line_length=100 17 | max-complexity = 15 18 | -------------------------------------------------------------------------------- /tests/unit/test_nothing.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | # import this so it's seen by coverage metrics 3 | from sqla_vertica_python import vertica_python # noqa 4 | 5 | 6 | class TestNothing(unittest.TestCase): 7 | # It shouldn't be too hard to increase coverage from here! 8 | def test_true(self): 9 | self.assertTrue(True) 10 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | version="${1:?version}" 4 | 5 | if [[ ${version:?} == v* ]] 6 | then 7 | >&2 echo "Please provide only numbers separated by dots" 8 | exit 1 9 | fi 10 | 11 | CIRCLE_TAG="v${version}" python setup.py verify 12 | 13 | git tag -m "Tag v${version:?}" -a "v${version}" "$(git rev-parse HEAD)" 14 | git push --follow-tags 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | /coverage.xml 29 | /cover 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | .ropeproject 39 | .idea/ 40 | -------------------------------------------------------------------------------- /deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | 4 | if [ "$(uname)" == Darwin ] 5 | then 6 | brew update && ( brew upgrade pyenv || true ) 7 | fi 8 | 9 | python_version=3.8.5 10 | # zipimport.ZipImportError: can't decompress data; zlib not available: 11 | # You may need `xcode-select --install` on OS X 12 | # https://github.com/pyenv/pyenv/issues/451#issuecomment-151336786 13 | pyenv install -s "${python_version:?}" 14 | if [ "$(uname)" == Darwin ] 15 | then 16 | # Python has needed this in the past when installed by 'pyenv 17 | # install'. The current version of 'psycopg2' seems to require it 18 | # now, but Python complains when it *is* set. 🤦 19 | CFLAGS="-I$(brew --prefix openssl)/include" 20 | export CFLAGS 21 | LDFLAGS="-L$(brew --prefix openssl)/lib" 22 | export LDFLAGS 23 | fi 24 | pyenv virtualenv "${python_version:?}" sqlalchemy-vertica-python-"${python_version:?}" || true 25 | pyenv local sqlalchemy-vertica-python-"${python_version:?}" 26 | 27 | pip3 install --upgrade pip 28 | 29 | pip3 install -r requirements.txt -e . 30 | -------------------------------------------------------------------------------- /Rakefile.quality: -------------------------------------------------------------------------------- 1 | require 'quality/rake/task' 2 | 3 | task :pronto do 4 | formatter = '-f github_pr text' if ENV.key? 'PRONTO_GITHUB_ACCESS_TOKEN' 5 | if ENV.key? 'TRAVIS_PULL_REQUEST' 6 | ENV['PRONTO_PULL_REQUEST_ID'] = ENV['TRAVIS_PULL_REQUEST'] 7 | elsif ENV.key? 'CIRCLE_PULL_REQUEST' 8 | ENV['PRONTO_PULL_REQUEST_ID'] = ENV['CIRCLE_PULL_REQUEST'].split('/').last 9 | end 10 | sh "pronto run #{formatter} -c origin/master --no-exit-code --unstaged "\ 11 | '|| true' 12 | sh "pronto run #{formatter} -c origin/master --no-exit-code --staged || true" 13 | sh "pronto run #{formatter} -c origin/master --no-exit-code || true" 14 | end 15 | 16 | task quality: %i[pronto] 17 | 18 | Quality::Rake::Task.new do |t| 19 | # t.verbose = true 20 | 21 | t.skip_tools = [ 22 | 'rails_best_practices', 23 | 'brakeman', 24 | 'bundle_audit', 25 | 'cane', 26 | 'flay', 27 | 'flog', 28 | 'pycodestyle', 29 | 'reek', 30 | 'rubocop', 31 | ] 32 | t.source_files_exclude_glob = '{.circleci/config.yml}' 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Locus Energy 2 | Copyright (c) 2013 James Casbon 3 | Copyright (c) 2010 Bo Shi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | 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 | sqlalchemy-vertica-python 2 | ========================= 3 | 4 | Vertica dialect for sqlalchemy. Forked from the `Vertica ODBC dialect `_, written by `James Casbon `_. 5 | 6 | This module implements a Vertica dialect for SQLAlchemy using the pure-Python DB-API driver `vertica-python `_, as adapted by `Luke Emery-Fertitta `_. 7 | 8 | It is currently maintained by `BlueLabs `_ - PRs are welcome! 9 | 10 | Engine creation: 11 | 12 | .. code-block:: python 13 | 14 | import sqlalchemy as sa 15 | sa.create_engine('vertica+vertica_python://user:pwd@host:port/database') 16 | 17 | Installation 18 | ------------ 19 | 20 | From PyPI: :: 21 | 22 | pip install sqlalchemy-vertica-python 23 | 24 | From git: :: 25 | 26 | git clone https://github.com/bluelabsio/vertica-sqlalchemy-python 27 | cd vertica-sqlalchemy-python 28 | python setup.py install 29 | 30 | 31 | Usage 32 | ------------ 33 | 34 | **ID/Primary Key Declaration** 35 | 36 | Do not use this. The INSERT will fail as it will try to insert the ID 37 | 38 | id = Column(Integer, primary_key=True) 39 | 40 | Do the following instead 41 | 42 | id = Column(Integer, Sequence('user_id_seq'), primary_key=True) 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: coverageclean test coverage quality 2 | 3 | coverageclean: 4 | rm -fr .coverage 5 | 6 | clean: coverageclean typecoverageclean 7 | FILES=$$(find . -name \*.pyc); for f in $${FILES}; do rm $$f; done 8 | 9 | test: 10 | ENV=test nosetests --exclude='tests/integration' --cover-package=sqla_vertica_python --with-coverage --with-xunit --cover-html --cover-xml --nocapture --cover-inclusive 11 | 12 | citest: 13 | ENV=test nosetests --exclude='tests/integration' --cover-package=sqla_vertica_python --with-coverage --with-xunit --cover-html --cover-xml --nocapture --cover-inclusive --xunit-file=test-reports/junit.xml 14 | 15 | coverage: 16 | python setup.py coverage_ratchet 17 | 18 | cicoverage: coverage 19 | @echo "Looking for un-checked-in unit test coverage metrics..." 20 | @git status --porcelain metrics/coverage_high_water_mark 21 | @test -z "$$(git status --porcelain metrics/coverage_high_water_mark)" 22 | 23 | flake8: 24 | flake8 sqla_vertica_python tests 25 | 26 | quality-flake8: 27 | make QUALITY_TOOL=flake8 quality 28 | 29 | quality-punchlist: 30 | make QUALITY_TOOL=punchlist quality 31 | 32 | # to run a single item, you can do: make QUALITY_TOOL=bigfiles quality 33 | quality: 34 | @quality_gem_version=$$(python -c 'import yaml; print(yaml.safe_load(open(".circleci/config.yml","r"))["quality_gem_version"])'); \ 35 | docker run \ 36 | -v "$$(pwd):/usr/app" \ 37 | -v "$$(pwd)/Rakefile.quality:/usr/quality/Rakefile" \ 38 | "apiology/quality:$${quality_gem_version}" ${QUALITY_TOOL} 39 | 40 | package: 41 | python3 setup.py sdist bdist_wheel 42 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from distutils.cmd import Command 3 | from setuptools import setup 4 | from setuptools.command.install import install 5 | import os.path 6 | import sys 7 | 8 | 9 | __version__ = '0.6.3' 10 | 11 | 12 | # From https://circleci.com/blog/continuously-deploying-python-packages-to-pypi-with-circleci/ 13 | class VerifyVersionCommand(install): 14 | """Custom command to verify that the git tag matches our version""" 15 | description = 'verify that the git tag matches our version' 16 | 17 | def run(self): 18 | from setup import __version__ 19 | tag = os.getenv('CIRCLE_TAG') 20 | tag_formatted_version = 'v{}'.format(__version__) 21 | 22 | if tag != tag_formatted_version: 23 | info = "Git tag: {0} does not match the version of this app: {1}".format( 24 | tag, __version__ 25 | ) 26 | sys.exit(info) 27 | 28 | 29 | class CoverageRatchetCommand(Command): 30 | description = 'Run coverage ratchet' 31 | user_options = [] # type: ignore 32 | 33 | def finalize_options(self) -> None: 34 | pass 35 | 36 | def run(self) -> None: 37 | """Run command.""" 38 | import xml.etree.ElementTree as ET 39 | 40 | tree = ET.parse(self.coverage_source_file) 41 | new_coverage = Decimal(tree.getroot().attrib["line-rate"]) * 100 42 | 43 | if not os.path.exists(self.coverage_file): 44 | with open(self.coverage_file, 'w') as f: 45 | f.write('0') 46 | 47 | with open(self.coverage_file, 'r') as f: 48 | high_water_mark = Decimal(f.read()) 49 | 50 | if new_coverage < high_water_mark: 51 | raise Exception( 52 | "{} coverage used to be {}; " 53 | "down to {}%. Fix by viewing '{}'".format(self.type_of_coverage, 54 | high_water_mark, 55 | new_coverage, 56 | self.coverage_url)) 57 | elif new_coverage > high_water_mark: 58 | with open(self.coverage_file, 'w') as f: 59 | f.write(str(new_coverage)) 60 | print("Just ratcheted coverage up to {}%".format(new_coverage)) 61 | else: 62 | print("Code coverage steady at {}%".format(new_coverage)) 63 | 64 | 65 | class TestCoverageRatchetCommand(CoverageRatchetCommand): 66 | def initialize_options(self) -> None: 67 | """Set default values for options.""" 68 | self.type_of_coverage = 'Test' 69 | self.coverage_url = 'cover/index.html' 70 | self.coverage_file = os.path.join( 71 | os.path.dirname(os.path.abspath(__file__)), 72 | 'metrics', 73 | 'coverage_high_water_mark' 74 | ) 75 | self.coverage_source_file = "coverage.xml" 76 | 77 | 78 | setup( 79 | name='sqlalchemy-vertica-python', 80 | version=__version__, 81 | description='Vertica dialect for sqlalchemy using vertica_python', 82 | long_description=open("README.rst").read(), 83 | license="MIT", 84 | url='https://github.com/bluelabsio/sqlalchemy-vertica-python', 85 | download_url = 'https://github.com/bluelabsio/sqlalchemy-vertica-python/tarball/{}'.format(__version__), 86 | author='James Casbon, Luke Emery-Fertitta', 87 | maintainer='Vince Broz', 88 | maintainer_email='opensource@bluelabs.com', 89 | packages=[ 90 | 'sqla_vertica_python', 91 | ], 92 | keywords=['sqlalchemy', 'vertica', 'python'], 93 | classifiers=[ 94 | 'Development Status :: 3 - Alpha', 95 | 'License :: OSI Approved :: MIT License', 96 | 'Programming Language :: Python :: 3.5', 97 | 'Programming Language :: Python :: 3.6', 98 | 'Programming Language :: Python :: 3.7', 99 | 'Programming Language :: Python :: 3.8', 100 | ], 101 | entry_points=""" 102 | [sqlalchemy.dialects] 103 | vertica.vertica_python = sqla_vertica_python.vertica_python:VerticaDialect 104 | """, 105 | install_requires=[ 106 | 'vertica_python' 107 | ], 108 | cmdclass={ 109 | 'coverage_ratchet': TestCoverageRatchetCommand, 110 | 'verify': VerifyVersionCommand, 111 | }, 112 | ) 113 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | quality_gem_version: &quality_gem_version python-37 4 | 5 | orbs: 6 | quality: bluelabs/quality@0.0.2 7 | 8 | commands: 9 | installvenv: 10 | description: "Installs dependencies in venv if needed" 11 | parameters: 12 | python_version: 13 | type: string 14 | steps: 15 | - restore_cache: 16 | key: deps-v2-<>-{{ .Branch }}-{{ checksum "requirements.txt" }}-{{ checksum "setup.py" }} 17 | - run: 18 | name: Install python deps in venv 19 | command: | 20 | if [ -f venv/bin/activate ] 21 | then 22 | . venv/bin/activate 23 | else 24 | python -m venv venv 25 | . venv/bin/activate 26 | pip install --upgrade pip 27 | pip install --progress-bar=off -r requirements.txt -e . 28 | fi 29 | # venv/ dir doesn't seem to save enough info to keep the 30 | # '-e .' installation. 31 | pip install --progress-bar=off -e . 32 | - save_cache: 33 | key: deps-v2-<>-{{ .Branch }}-{{ checksum "requirements.txt" }}-{{ checksum "setup.py" }} 34 | paths: 35 | - "venv" 36 | 37 | jobs: 38 | test: 39 | parameters: 40 | python_version: 41 | type: string 42 | description: "Version of python to test against" 43 | coverage: 44 | type: boolean 45 | # The python version can affect the mypy and test coverage in 46 | # subtle ways - coverage enforcement should be set only on the 47 | # version which matches local development. 48 | default: false 49 | description: "Enforce coverage not slipping" 50 | docker: 51 | - image: cimg/python:<> 52 | steps: 53 | - checkout 54 | - installvenv: 55 | python_version: <> 56 | - run: 57 | name: Run tests 58 | command: | 59 | . venv/bin/activate 60 | make citest 61 | if [ "<>" == true ] 62 | then 63 | make cicoverage 64 | fi 65 | - run: 66 | when: always 67 | name: Archive coverage report 68 | command: | 69 | tar -czvf cover.tar.gz cover 70 | - store_test_results: 71 | path: test-reports 72 | - store_artifacts: 73 | path: test-reports 74 | - store_artifacts: 75 | path: cover.tar.gz 76 | deploy: 77 | parameters: 78 | python_version: 79 | type: string 80 | description: "Version of python to test against" 81 | default: '3.8' 82 | docker: 83 | - image: cimg/python:<> 84 | steps: 85 | - checkout 86 | - installvenv: 87 | python_version: <> 88 | - run: 89 | name: verify git tag vs. version 90 | command: | 91 | . venv/bin/activate 92 | python3 setup.py verify 93 | - run: 94 | name: init .pypirc 95 | command: | 96 | cat >> $HOME/.pypirc \<