diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..f7059a28 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + + # Maintain dependencies for poetry + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 5 \ No newline at end of file diff --git a/.github/workflows/CI-CD.yml b/.github/workflows/ci-cd.yml similarity index 61% rename from .github/workflows/CI-CD.yml rename to .github/workflows/ci-cd.yml index 28a9594b..863cff43 100644 --- a/.github/workflows/CI-CD.yml +++ b/.github/workflows/ci-cd.yml @@ -13,10 +13,18 @@ jobs: strategy: fail-fast: false matrix: - python: [3.7, 3.8, 3.9] + python: + - "3.8" + - "3.9" + - "3.10" connector: - pyodbc - - turbodbc + # TODO: Reenable once the issue(s) below have been fixed. + # Turbodbc can't be supported using poetry until: + # - https://github.com/blue-yonder/turbodbc/issues/358 + # - https://github.com/exasol/sqlalchemy-exasol/issues/146 + # are fixed. + # - turbodbc exasol_version: - 7.1.6 - 7.0.16 @@ -31,26 +39,24 @@ jobs: - name: Setup integration-test-docker-environment uses: actions/setup-python@v2 + - name: Install via apt + run: sudo apt-get install unixodbc unixodbc-dev libboost-date-time-dev libboost-locale-dev libboost-system-dev + - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - - name: Install via apt - run: sudo apt-get install unixodbc unixodbc-dev libboost-date-time-dev libboost-locale-dev libboost-system-dev - - - name: Install pipenv - uses: dschep/install-pipenv-action@v1 - - - name: Pip install test requirements - uses: BSFishy/pip-action@v1 + - name: Install poetry + uses: abatilo/actions-poetry@v2.0.0 with: - requirements: requirements_dev.txt + poetry-version: 1.1.13 - - name: Pip install extra requirements - uses: BSFishy/pip-action@v1 - with: - requirements: requirements_extras.txt + - name: Install python project dependencies + run: poetry install + + - name: Install python project dependencies including trubodbc + run: poetry install --extras "turbodbc" if: ${{ matrix.connector == 'turbodbc' }} - name: Checkout test environment @@ -60,11 +66,8 @@ jobs: ITDE_URL: "https://github.com/exasol/integration-test-docker-environment.git" ITDE_TAG: "0.10.0" - - name: Check documentation links ${{ matrix.python }} using ${{ matrix.connector }} - run: nox -s "check-links" - - name: Run Test for Python ${{ matrix.python }} using ${{ matrix.connector }} - run: nox -s "verify(connector='${{ matrix.connector }}')" + run: poetry run nox -s "verify(connector='${{ matrix.connector }}')" upload_to_pypi: runs-on: ubuntu-latest @@ -72,11 +75,9 @@ jobs: if: startsWith(github.event.ref, 'refs/tags') strategy: matrix: - python: [3.7] - connector: - - pyodbc + python: [3.8] - name: Upload to PyPI (Python-${{ matrix.python }}, Connector-${{ matrix.connector }}) + name: Build & Upload Package [PYPI] steps: @@ -91,17 +92,13 @@ jobs: with: python-version: ${{ matrix.python }} - - name: Install pipenv - uses: dschep/install-pipenv-action@v1 - - - name: Install Wheel - run: pip install wheel - - - name: Build sdist and wheel packages - run: python setup.py sdist bdist_wheel - - - name: Push package to Pypi - uses: pypa/gh-action-pypi-publish@release/v1 + - name: Install poetry + uses: abatilo/actions-poetry@v2.0.0 with: - user: __token__ - password: ${{ secrets.pypi_token }} + poetry-version: 1.1.13 + + - name: Build and push package to PYPI + env: + POETRY_HTTP_BASIC_TEST_USERNAME: "__token__" + POETRY_HTTP_BASIC_TEST_PASSWORD: "${{ secrets.pypi_token }}" + run: poetry run nox -s release diff --git a/.github/workflows/CI.yml b/.github/workflows/ci.yaml similarity index 62% rename from .github/workflows/CI.yml rename to .github/workflows/ci.yaml index 7a6d9a85..06c064bd 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/ci.yaml @@ -17,10 +17,18 @@ jobs: strategy: fail-fast: false matrix: - python: [3.7, 3.8, 3.9] + python: + - "3.8" + - "3.9" + - "3.10" connector: - pyodbc - - turbodbc + # TODO: Reenable once the issue(s) below have been fixed. + # Turbodbc can't be supported using poetry until: + # - https://github.com/blue-yonder/turbodbc/issues/358 + # - https://github.com/exasol/sqlalchemy-exasol/issues/146 + # are fixed. + # - turbodbc exasol_version: - 7.1.6 - 7.0.16 @@ -32,29 +40,24 @@ jobs: - name: Fetch sqlalchemy_exasol code from repository uses: actions/checkout@v2 - - name: Setup integration-test-docker-environment - uses: actions/setup-python@v2 + - name: Install via apt + run: sudo apt-get install unixodbc unixodbc-dev libboost-date-time-dev libboost-locale-dev libboost-system-dev - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} - - name: Install via apt - run: sudo apt-get install unixodbc unixodbc-dev libboost-date-time-dev libboost-locale-dev libboost-system-dev - - - name: Install pipenv - uses: dschep/install-pipenv-action@v1 - - - name: Pip install requirements - uses: BSFishy/pip-action@v1 + - name: Install poetry + uses: abatilo/actions-poetry@v2.0.0 with: - requirements: requirements_dev.txt + poetry-version: 1.1.13 - - name: Pip install extra requirements - uses: BSFishy/pip-action@v1 - with: - requirements: requirements_extras.txt + - name: Install python project dependencies + run: poetry install + + - name: Install python project dependencies including trubodbc + run: poetry install --extras "turbodbc" if: ${{ matrix.connector == 'turbodbc' }} - name: Checkout test environment @@ -64,22 +67,17 @@ jobs: ITDE_URL: "https://github.com/exasol/integration-test-docker-environment.git" ITDE_TAG: "0.10.0" - - name: Check documentation links ${{ matrix.python }} using ${{ matrix.connector }} - run: nox -s "check-links" - - name: Run Test for Python ${{ matrix.python }} using ${{ matrix.connector }} - run: nox -s "verify(connector='${{ matrix.connector }}')" + run: poetry run nox -s "verify(connector='${{ matrix.connector }}')" build_package: runs-on: ubuntu-latest needs: run_tests strategy: matrix: - python: [3.7] - connector: - - pyodbc + python: [3.8] - name: Build Package (Python-${{ matrix.python }}, Connector-${{ matrix.connector }}) + name: Build Package steps: @@ -90,15 +88,14 @@ jobs: run: git fetch origin +refs/tags/*:refs/tags/* - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} - - name: Install pipenv - uses: dschep/install-pipenv-action@v1 - - - name: Install Wheel - run: pip install wheel + - name: Install poetry + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: 1.1.13 - name: Build sdist and wheel packages - run: python setup.py sdist bdist_wheel + run: poetry build diff --git a/.github/workflows/link-check.yaml b/.github/workflows/link-check.yaml new file mode 100644 index 00000000..8bd87db3 --- /dev/null +++ b/.github/workflows/link-check.yaml @@ -0,0 +1,40 @@ +name: Check Links + +on: + push: + branches-ignore: + - 'master' + pull_request: + branches-ignore: + - 'master' + +jobs: + run_tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python: [3.8] + + name: Check Links (Python-${{ matrix.python }}) + + steps: + + - name: Fetch sqlalchemy_exasol code from repository + uses: actions/checkout@v2 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python }} + + - name: Install poetry + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: 1.1.13 + + - name: Install python project dependencies + run: poetry install --no-root + + - name: Check documentation links ${{ matrix.python }} using ${{ matrix.connector }} + run: poetry run nox -s "check-links" diff --git a/.gitignore b/.gitignore index 460c6be0..34ab2a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +odbcconfig/odbcinst.ini + *.py[cod] .build_output @@ -24,7 +26,6 @@ develop-eggs .installed.cfg lib lib64 -sqlalchemy_exasol/_version.py # Installer logs pip-log.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..53fb7fe1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +default_stages: [commit] +repos: +- repo: local + hooks: + + - id: version-check + name: Check if version(s) are in sync + always_run: true + language: system + entry: python scripts/version_check.py --fix sqlalchemy_exasol/version.py + +- repo: https://github.com/asottile/pyupgrade + rev: v2.34.0 + hooks: + - id: pyupgrade + args: ['--py38-plus'] + types: [python] diff --git a/CHANGES.md b/CHANGES.md index 31e97333..8c5cf1ca 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,16 @@ +# 3.0.0 +⚠️ ATTENTION ⚠️ + * The support of the turbodbc feature have been suspended, until the following issues have been addressed + * https://github.com/blue-yonder/turbodbc/issues/358 + * https://github.com/exasol/sqlalchemy-exasol/issues/146 + +Note: If you depend on turbodbc we suggest you to use the latest version supporting it (2.4.0) + +* Dropped python 3.7 support + * If you still depend on python 3.7 use the 2.x version line +* Update project setup/structure to pyproject.toml based project setup (poetry) +* Removed conda forge support + # 2.4.0 * Fixed bug when accessing underlying odbc connection while using NullPool based engine (Note: this addresses the superset [issue-20105](https://github.com/apache/superset/issues/20105)) diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index b0c2ac4c..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include versioneer.py -include sqlalchemy_exasol/_version.py -include README.rst diff --git a/README.rst b/README.rst index 6abd406e..30955e41 100644 --- a/README.rst +++ b/README.rst @@ -3,13 +3,10 @@ SQLAlchemy Dialect for EXASOL DB .. image:: https://github.com/exasol/sqlalchemy_exasol/workflows/CI-CD/badge.svg?branch=master - :target: https://github.com/exasol/sqlalchemy_exasol/actions?query=workflow%3ACI-CD + :target: https://github.com/exasol/sqlalchemy_exasol/actions?query=workflow%3ACI .. image:: https://img.shields.io/pypi/v/sqlalchemy_exasol :target: https://pypi.org/project/sqlalchemy-exasol/ :alt: PyPI Version -.. image:: https://img.shields.io/conda/vn/conda-forge/sqlalchemy_exasol.svg - :target: https://anaconda.org/conda-forge/sqlalchemy_exasol - :alt: Conda Version This is an SQLAlchemy dialect for the EXASOL database. @@ -40,7 +37,7 @@ On Linux/Unix like systems you need: Turbodbc support ```````````````` -- You can use Turbodbc with sqlalchemy_exasol if you use a python version >= 3.7. +- You can use Turbodbc with sqlalchemy_exasol if you use a python version >= 3.8. - Multi row update is not supported, see `test/test_update.py `_ for an example diff --git a/TODOs.txt b/TODOs.txt new file mode 100644 index 00000000..a1f0df32 --- /dev/null +++ b/TODOs.txt @@ -0,0 +1,7 @@ +FINAL TASKS: +------------ +* ATTENTION: Update turbodbc driver to latest including installation fix! +* make sure automated publish via GH action still works with poetry +* CLOSE: https://github.com/exasol/sqlalchemy-exasol/issues/135 +* CLOSE: https://github.com/exasol/sqlalchemy-exasol/issues/146 +* CLOSE: https://github.com/exasol/sqlalchemy-exasol/issues/128 diff --git a/doc/developer_guide/developer_guide.rst b/doc/developer_guide/developer_guide.rst index 7536796a..54819b04 100644 --- a/doc/developer_guide/developer_guide.rst +++ b/doc/developer_guide/developer_guide.rst @@ -8,7 +8,8 @@ If you want to engage in development of this project you should have the followi Tools +++++ -* python_ >= 3.7 +* python_ >= 3.8 +* poetry_ >= 1.1.0 * git_ * Docker_ * integration-test-docker-environment_ @@ -61,6 +62,59 @@ Project Layout | ... +Setup Your Workspace +-------------------- + +Get The Source +++++++++++++++ + +.. code-block:: + + git clone https://github.com/exasol/sqlalchemy-exasol.git + +Setup the Tooling & Virtual Environment ++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: + + poetry shell + poetry install + +.. warning:: + + make sure you have the poetry shell active whenever you want to work in the workspace + +Install the Git Commit Hooks +++++++++++++++++++++++++++++ + +.. code-block:: + + pre-commit install + +.. note:: + + This may need to be rerun if you want or do add non standard hook types, for further details + see `pre-commit install -h`. + + +Task Runner (Nox) +----------------- +Most repeating and complex tasks within this project are automated using the task runner `nox`. +To get an overview about the available `tasks` just run: + +.. code-block:: + + nox -l + +All task(s) which will be run by default will have a leading `*`. +In order to run a specific task execute the following command: + +.. code-block:: + + nox -s + +You can modify or add new task by editing the file `noxfile.py`. + Tests ----- @@ -68,7 +122,8 @@ Tests .. code-block:: - pip install -r requirements_dev.txt + # make sure you are using the virtual environment poetry has setup for this project + poetry shell #. Run all tests with `pyodbc` connector @@ -96,6 +151,7 @@ Tests .. _action: https://github.com/exasol/sqlalchemy_exasol/actions .. _python: https://www.python.org/ +.. _poetry: https://python-poetry.org/ .. _git: https://git-scm.com/ .. _Docker: https://www.docker.com/ .. _integration-test-docker-environment: https://github.com/exasol/integration-test-docker-environment diff --git a/noxfile.py b/noxfile.py index dee89fd1..7660237b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,5 +1,6 @@ import os import sys +from argparse import ArgumentParser from contextlib import contextmanager from pathlib import Path from tempfile import TemporaryDirectory @@ -11,10 +12,16 @@ sys.path.append(f"{SCRIPTS}") import nox +from git import tags from links import check as _check from links import documentation as _documentation from links import urls as _urls from pyodbc import connect +from version_check import ( + version_from_poetry, + version_from_python_module, + version_from_string, +) # default actions to be run if nothing is explicitly specified with the -s option nox.options.sessions = ["verify(connector='pyodbc')"] @@ -27,6 +34,7 @@ class Settings: ENVIRONMENT_NAME = "test" DB_PORT = 8888 BUCKETFS_PORT = 6666 + VERSION_FILE = PROJECT_ROOT / "sqlalchemy_exasol" / "version.py" ODBCINST_INI_TEMPLATE = dedent( @@ -90,10 +98,22 @@ def odbcconfig(): yield cfg, env -@nox.session +@nox.session(python=False) @nox.parametrize("connector", Settings.CONNECTORS) def verify(session, connector): """Prepare and run all available tests""" + + def is_version_in_sync(): + return ( + version_from_python_module(Settings.VERSION_FILE) == version_from_poetry() + ) + + if not is_version_in_sync(): + session.error( + "Versions out of sync, version file:" + f"{version_from_python_module(Settings.VERSION_FILE)}," + f"poetry: {version_from_poetry()}." + ) session.notify(find_session_runner(session, "db-start")) session.notify( find_session_runner(session, f"integration(connector='{connector}')") @@ -101,7 +121,7 @@ def verify(session, connector): session.notify(find_session_runner(session, "db-stop")) -@nox.session(name="db-start", reuse_venv=True) +@nox.session(name="db-start", python=False) def start_db(session): """Start the test database""" @@ -154,14 +174,14 @@ def populate(): populate() -@nox.session(name="db-stop", reuse_venv=True) +@nox.session(name="db-stop", python=False) def stop_db(session): """Stop the test database""" session.run("docker", "kill", "db_container_test", external=True) session.run("docker", "kill", "test_container_test", external=True) -@nox.session +@nox.session(python=False) @nox.parametrize("connector", Settings.CONNECTORS) def integration(session, connector): """Run(s) the integration tests for a specific connector. Expects a test database to be available.""" @@ -177,7 +197,7 @@ def integration(session, connector): session.run("pytest", "--dropfirst", "--dburi", uri, external=True, env=env) -@nox.session(name="report-skipped", python=None) +@nox.session(name="report-skipped", python=False) def report_skipped(session): """ Runs all tests for all supported connectors and creates a csv report of skipped tests for each connector. @@ -219,7 +239,7 @@ def report_skipped(session): ) -@nox.session(name="check-links", python=None) +@nox.session(name="check-links", python=False) def check_links(session): """Checks weather or not all links in the documentation can be accessed""" errors = [] @@ -231,12 +251,51 @@ def check_links(session): if errors: session.error( "\n" - + "\n".join((f"Url: {e[1]}, File: {e[0]}, Error: {e[3]}" for e in errors)) + + "\n".join(f"Url: {e[1]}, File: {e[0]}, Error: {e[3]}" for e in errors) ) -@nox.session(name="list-links", python=None) +@nox.session(name="list-links", python=False) def list_links(session): """List all links within the documentation""" for path, url in _urls(_documentation(PROJECT_ROOT)): session.log(f"Url: {url}, File: {path}") + + +@nox.session(python=False) +def release(session: nox.Session): + def create_parser(): + p = ArgumentParser( + "Release a pypi package", + usage="nox -s release -- [-h] [-d]", + ) + p.add_argument("-d", "--dry-run", action="store_true", help="just do a dry run") + return p + + args = [] + parser = create_parser() + cli_args = parser.parse_args(session.posargs) + if cli_args.dry_run: + args.append("--dry-run") + + version_file = version_from_python_module(Settings.VERSION_FILE) + module_version = version_from_poetry() + git_version = version_from_string(tags()[-1]) + + if not (module_version == git_version == version_file): + session.error( + f"Versions out of sync, version file: {version_file}, poetry: {module_version}, tag: {git_version}." + ) + + session.run( + "poetry", + "build", + external=True, + ) + + session.run( + "poetry", + "publish", + *args, + external=True, + ) diff --git a/odbcconfig/odbcinst.ini b/odbcconfig/odbcinst.ini deleted file mode 100644 index 7769ff75..00000000 --- a/odbcconfig/odbcinst.ini +++ /dev/null @@ -1,7 +0,0 @@ -[ODBC] -#Trace=yes -#TraceFile=~/odbc.trace - -[EXAODBC] -#Driver location will be appended in build environment: -#DRIVER= diff --git a/odbcconfig/odbcinst.ini.template b/odbcconfig/odbcinst.ini.template new file mode 100644 index 00000000..1e259a6a --- /dev/null +++ b/odbcconfig/odbcinst.ini.template @@ -0,0 +1,7 @@ +[ODBC] +#Trace=yes +#TraceFile=~/odbc.trace + +[EXAODBC] +#Driver location needs to be added in build environment: +DRIVER= diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..7b3d597d --- /dev/null +++ b/poetry.lock @@ -0,0 +1,565 @@ +[[package]] +name = "argcomplete" +version = "1.12.3" +description = "Bash tab completion for argparse" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["coverage", "flake8", "pexpect", "wheel"] + +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorlog" +version = "6.6.0" +description = "Add colours to the output of Python's logging module." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "coverage" +version = "6.4.2" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "distlib" +version = "0.3.4" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.7.1" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + +[[package]] +name = "identify" +version = "2.5.1" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" + +[[package]] +name = "nox" +version = "2022.1.7" +description = "Flexible test automation." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +argcomplete = ">=1.9.4,<2.0" +colorlog = ">=2.6.1,<7.0.0" +packaging = ">=20.9" +py = ">=1.4.0,<2.0.0" +virtualenv = ">=14.0.0" + +[package.extras] +tox_to_nox = ["jinja2", "tox"] + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "pre-commit" +version = "2.20.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyodbc" +version = "4.0.32" +description = "DB API Module for ODBC" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-json-report" +version = "1.5.0" +description = "A pytest plugin to report test results as JSON files" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pytest = ">=3.8.0" +pytest-metadata = "*" + +[[package]] +name = "pytest-metadata" +version = "1.11.0" +description = "pytest plugin for test session metadata" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +pytest = ">=2.9.0" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "sqlalchemy" +version = "1.3.24" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mysql = ["mysqlclient"] +oracle = ["cx-oracle"] +postgresql = ["psycopg2"] +postgresql_pg8000 = ["pg8000 (<1.16.6)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urlscan" +version = "0.9.9" +description = "View/select the URLs in an email message or file" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +urwid = ">=1.2.1" + +[[package]] +name = "urwid" +version = "2.1.2" +description = "A full-featured console (xterm et al.) user interface library" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "virtualenv" +version = "20.15.1" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +platformdirs = ">=2,<3" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] + +[extras] +turbodbc = [] + +[metadata] +lock-version = "1.1" +python-versions = ">=3.8,<4.0" +content-hash = "20a4b8a3436e8ec9db41a1493e8c5c384e2143f37824acf519f18d184634319e" + +[metadata.files] +argcomplete = [ + {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"}, + {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"}, +] +atomicwrites = [] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +colorlog = [ + {file = "colorlog-6.6.0-py2.py3-none-any.whl", hash = "sha256:351c51e866c86c3217f08e4b067a7974a678be78f07f85fc2d55b8babde6d94e"}, + {file = "colorlog-6.6.0.tar.gz", hash = "sha256:344f73204009e4c83c5b6beb00b3c45dc70fcdae3c80db919e0a4171d006fde8"}, +] +coverage = [] +distlib = [ + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, +] +filelock = [ + {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"}, + {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"}, +] +identify = [ + {file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"}, + {file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +nodeenv = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] +nox = [ + {file = "nox-2022.1.7-py3-none-any.whl", hash = "sha256:efee12f02d39405b16d68f60e7a06fe1fc450ae58669d6cdda8c7f48e3bae9e3"}, + {file = "nox-2022.1.7.tar.gz", hash = "sha256:b375238cebb0b9df2fab74b8d0ce1a50cd80df60ca2e13f38f539454fcd97d7e"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +pre-commit = [] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pyodbc = [ + {file = "pyodbc-4.0.32-cp27-cp27m-win32.whl", hash = "sha256:2152ce6d5131d769ff5839aa762e12d844c95e9ec4bb2f666e8cd9dfa1ae2240"}, + {file = "pyodbc-4.0.32-cp27-cp27m-win_amd64.whl", hash = "sha256:56ec4974096d40d6c62a228799122dbc2ade6c4045cc5d31860212a32cae95b1"}, + {file = "pyodbc-4.0.32-cp36-cp36m-win32.whl", hash = "sha256:699c080b1c1f7b4afc368b3521fd1161f46a10223443692a249cb01d90949b31"}, + {file = "pyodbc-4.0.32-cp36-cp36m-win_amd64.whl", hash = "sha256:0d4e14adb149cae45da37fa87aa297055156dae6e89ca3c75493d3d62d78e543"}, + {file = "pyodbc-4.0.32-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6c1e1c1fe747b0f6419e8df0b5c43161e7437dbf72f93f9fcfb9b7358fad3e12"}, + {file = "pyodbc-4.0.32-cp37-cp37m-win32.whl", hash = "sha256:bbc07517f339e019ee9f1fe679c4241251d11ca2124567616f67d62e73c29fc0"}, + {file = "pyodbc-4.0.32-cp37-cp37m-win_amd64.whl", hash = "sha256:e81ebf9cab80a6eaba7922dea02036e9f8a507a7b818856b8008a02d6fc0d2ab"}, + {file = "pyodbc-4.0.32-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0e4178e9b93329bbba17555882008e36a114179d06033b813a13b254dcd755d0"}, + {file = "pyodbc-4.0.32-cp38-cp38-win32.whl", hash = "sha256:c066f032e69fd71e9fadb3a380dfe8ecd1728b40a2bf38f76054d284f8523b29"}, + {file = "pyodbc-4.0.32-cp38-cp38-win_amd64.whl", hash = "sha256:736acad1b264ddb7313058dfe37265b0c5160c1c2a9d1ffd391347c025eb5dd1"}, + {file = "pyodbc-4.0.32-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:339d8aa633b0c65be5149c3378c7e3b5bead94dc8bb023a715b416bd047a008e"}, + {file = "pyodbc-4.0.32-cp39-cp39-win_amd64.whl", hash = "sha256:cda790bdc25bfad12d4fb9ba93368275802f7f9ecfa4c9c65e982d3a7fc35f2e"}, + {file = "pyodbc-4.0.32.tar.gz", hash = "sha256:9be5f0c3590655e1968488410fe3528bb8023d527e7ccec1f663d64245071a6b"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [] +pytest-cov = [ + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, +] +pytest-json-report = [ + {file = "pytest-json-report-1.5.0.tar.gz", hash = "sha256:2dde3c647851a19b5f3700729e8310a6e66efb2077d674f27ddea3d34dc615de"}, + {file = "pytest_json_report-1.5.0-py3-none-any.whl", hash = "sha256:9897b68c910b12a2e48dd849f9a284b2c79a732a8a9cb398452ddd23d3c8c325"}, +] +pytest-metadata = [ + {file = "pytest-metadata-1.11.0.tar.gz", hash = "sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1"}, + {file = "pytest_metadata-1.11.0-py2.py3-none-any.whl", hash = "sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sqlalchemy = [ + {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, + {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +urlscan = [ + {file = "urlscan-0.9.9-py3-none-any.whl", hash = "sha256:864f5332520f4fafe67d68e023aefc5636993cedb69145e4d9bb3a2884ee34ed"}, + {file = "urlscan-0.9.9.tar.gz", hash = "sha256:da66b99b3364f97d11ecfc105b8c95dd155bd08d8313dd9cc6472791493480d1"}, +] +urwid = [ + {file = "urwid-2.1.2.tar.gz", hash = "sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"}, +] +virtualenv = [ + {file = "virtualenv-20.15.1-py2.py3-none-any.whl", hash = "sha256:b30aefac647e86af6d82bfc944c556f8f1a9c90427b2fb4e3bfbf338cb82becf"}, + {file = "virtualenv-20.15.1.tar.gz", hash = "sha256:288171134a2ff3bfb1a2f54f119e77cd1b81c29fc1265a2356f3e8d14c7d58c4"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..9fa583b7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,88 @@ +[build-system] +requires = [ + "poetry>=1.0.0", +] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "sqlalchemy_exasol" +version = "3.0.0" +description = "EXASOL dialect for SQLAlchemy" +readme = "README.rst" +license = "License :: OSI Approved :: BSD License" +authors = [ + "Exasol AG ", + "Blue Yonder GmbH", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Operating System :: POSIX :: Linux", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Information Technology", + "License :: OSI Approved :: BSD License", + "Programming Language :: SQL", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Database", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Scientific/Engineering :: Information Analysis", +] +keywords = [ + "exasol", + "sql", + "sqlalchemy", + "data science", + "database", +] +repository = "https://github.com/exasol/sqlalchemy-exasol" +homepage = "https://www.exasol.com/" + +include = [ + "README.rst", + "CHANGES.md", + "LICENSE", +] +exclude = [] + + +[tool.poetry.dependencies] +python = ">=3.8,<4.0" +SQLAlchemy = ">=1.3.24,<1.4" +pyodbc = ">=4.0.32" +packaging = "^21.3" +# TODO: Reenable once the causing issues got fixed: +# - https://github.com/blue-yonder/turbodbc/issues/358 +# - https://github.com/exasol/sqlalchemy-exasol/issues/146 +# turbodbc = { version = ">4.5.3", optional = true } + +[tool.poetry.dev-dependencies] +nox = ">=2022.1.7" +urlscan = ">=0.9.9" +pytest-json-report = ">=1.5.0" +# The excluded versions mirror the excluded versions of sqla 1.3.X. +# The limitation/issue pytest <6 is tracked in https://github.com/exasol/sqlalchemy-exasol/issues/144 +pytest = ">=6,<7" +pytest-cov = ">=2.7.0" +pre-commit = "^2.19.0" +wheel = "^0.37.1" + +[tool.poetry.extras] +turbodbc = ["turbodbc"] + +[tool.poetry.plugins."sqlalchemy.dialects"] +"exa.pyodbc" = "sqlalchemy_exasol.pyodbc:EXADialect_pyodbc" +"exa.turbodbc" = "sqlalchemy_exasol.turbodbc:EXADialect_turbodbc" + +[tool.pytest.ini_options] +addopts= "--tb native -v -r fxX" +python_files= "test/*test_*.py" +filterwarnings = [ + "error::DeprecationWarning", + "ignore::DeprecationWarning:sqlalchemy.testing.plugin.*", +] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index fd806b74..00000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -SQLAlchemy==1.3.24 # rq.filter: >=1.3.0,<1.4 -pyodbc==4.0.32 # rq.filter: >=4,<5 diff --git a/requirements_dev.txt b/requirements_dev.txt deleted file mode 100644 index 80d8bb89..00000000 --- a/requirements_dev.txt +++ /dev/null @@ -1,5 +0,0 @@ --r requirements_test.txt -nox>=2022.1.7 -urlscan>=0.9.9 -pytest-json-report>=1.5.0 - diff --git a/requirements_extras.txt b/requirements_extras.txt deleted file mode 100644 index 078e187a..00000000 --- a/requirements_extras.txt +++ /dev/null @@ -1 +0,0 @@ -turbodbc==3.3.0 # 4.0.0 introduced some breaking change (e.g. drops Python 2.7 support) diff --git a/requirements_test.txt b/requirements_test.txt deleted file mode 100644 index bfb232af..00000000 --- a/requirements_test.txt +++ /dev/null @@ -1,4 +0,0 @@ --r requirements.txt -pytest>=3.1.0,<5.4 #TODO: test run fails with 5.4 -pytest-cov>=2.7.0 #avoid https://github.com/z4r/python-coveralls/issues/66 -mock>=2.0.0 diff --git a/scripts/git.py b/scripts/git.py new file mode 100644 index 00000000..6d73ebb1 --- /dev/null +++ b/scripts/git.py @@ -0,0 +1,15 @@ +from subprocess import PIPE, run + + +def tags(): + """ + Returns a list of all tags, sorted from [0] oldest to [-1] newest. + PreConditions: + - the git cli tool needs to be installed + - the git cli tool can be found via $PATH + - the code is executed where the working directory is within a git repository + """ + command = ["git", "tag", "--sort=committerdate"] + result = run(command, capture_output=True, check=True) + tags = (tag.strip() for tag in result.stdout.decode("utf-8").splitlines()) + return list(tags) diff --git a/scripts/version_check.py b/scripts/version_check.py new file mode 100644 index 00000000..84ade4c5 --- /dev/null +++ b/scripts/version_check.py @@ -0,0 +1,135 @@ +import argparse +import subprocess +import sys +from collections import namedtuple +from inspect import cleandoc +from pathlib import Path +from shutil import which + +Version = namedtuple("Version", ["major", "minor", "patch"]) + +_SUCCESS = 0 +_FAILURE = 1 + +# fmt: off +_VERSION_MODULE_TEMPLATE = cleandoc(''' +# ATTENTION: +# This file is generated, do not edit it manually! +# If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. + +MAJOR = {major} +MINOR = {minor} +PATCH = {patch} + +VERSION = f"{{MAJOR}}.{{MINOR}}.{{PATCH}}" +''') + "\n" + + +# fmt: on + + +def version_from_string(s): + """Converts a version string of the following format major.minor.patch to a version object""" + major, minor, patch = (int(number, base=0) for number in s.split(".")) + return Version(major, minor, patch) + + +class CommitHookError(Exception): + """Indicates that this commit hook encountered an error""" + + +def version_from_python_module(path): + """Retrieve version information from the `version` module""" + with open(path) as file: + _locals, _globals = {}, {} + exec(file.read(), _locals, _globals) + + try: + version = _globals["VERSION"] + except KeyError as ex: + raise CommitHookError("Couldn't find version within module") from ex + + return version_from_string(version) + + +def version_from_poetry(): + poetry = which("poetry") + if not poetry: + raise CommitHookError("Couldn't find poetry executable") + + result = subprocess.run([poetry, "version"], capture_output=True) + version = result.stdout.decode().split()[1] + return version_from_string(version) + + +def write_version_module(version, path, exists_ok=True): + version_file = Path(path) + if version_file.exists() and not exists_ok: + raise CommitHookError(f"Version file [{version_file}] already exists.") + version_file.unlink(missing_ok=True) + with open(version_file, "w") as f: + f.write( + _VERSION_MODULE_TEMPLATE.format( + major=version.major, minor=version.minor, patch=version.patch + ) + ) + + +def _create_parser(): + parser = argparse.ArgumentParser() + parser.add_argument("version_module", help="Path to version module") + parser.add_argument("files", nargs="*") + parser.add_argument( + "-d", + "--debug", + action="store_true", + default=False, + help="enabled debug mode for execution.", + ) + parser.add_argument( + "-f", + "--fix", + action="store_true", + default=False, + help="fix instead of check.", + ) + return parser + + +def _main_debug(args): + module_version = version_from_python_module(args.version_module) + poetry_version = version_from_poetry() + + if args.fix: + write_version_module(poetry_version, args.version_module) + + if not module_version == poetry_version: + print( + f"Version in pyproject.toml {poetry_version} and {args.version_module} {module_version} do not match!" + ) + if args.fix: + print( + f"Updating version in file ({args.version_module}) from {module_version} to {poetry_version}" + ) + return _FAILURE + + return _SUCCESS + + +def _main(args): + try: + return _main_debug(args) + except Exception as ex: + print(f"Error while executing program, details: {ex}", file=sys.stderr) + return _FAILURE + + +def main(argv=None): + parser = _create_parser() + args = parser.parse_args() + entry_point = _main if not args.debug else _main_debug + return entry_point(args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/setup.cfg b/setup.cfg index 3e519d5a..bb0aea60 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,20 +1,4 @@ -[egg_info] -#tag_build = dev - -[tool:pytest] -addopts= --tb native -v -r fxX -python_files=test/*test_*.py -filterwarnings = - error::DeprecationWarning - [sqla_testing] requirement_cls=sqlalchemy_exasol.requirements:Requirements profile_file=.profiles.txt -[db] -#specify default db connection string to run test against -#default=exa+pyodbc://USER:PWD@IP_RANGE:PORT/SCHEMA - -[bdist_wheel] -# Use this option if your package is pure-python -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 576a2cd9..00000000 --- a/setup.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Defines the setup for this package. - -Do not forget to document your changes in CHANGES.md - -:file: setup.py -:authors: Blue Yonder GmbH, Exasol AG -:date: 2014/02/11 -""" -import os - -from setuptools import setup - -# Get long_description from README.md: -here = os.path.dirname(os.path.abspath(__file__)) -long_description = "" -with open(os.path.join(here, "README.rst")) as f: - long_description = f.read().strip() - -setup( - name="sqlalchemy_exasol", - setup_requires=["setuptools_scm"], - license="License :: OSI Approved :: BSD License", - url="https://github.com/exasol/sqlalchemy-exasol", - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Topic :: Database", - ], - description="EXASOL dialect for SQLAlchemy", - long_description=long_description, - author="Blue Yonder GmbH, Exasol AG", - author_email="opensource@exasol.com", - packages=["sqlalchemy_exasol"], - install_requires=["SQLAlchemy>=1.3.24, <1.4", "pyodbc>=4.0.32", "six>=1.5"], - extras_require={"turbodbc": ["turbodbc>=3.3.0, <4"]}, - tests_require=["pytest", "mock>=1.0.1"], - test_suite="pytest.main", - use_scm_version={"write_to": "sqlalchemy_exasol/_version.py"}, - entry_points={ - "sqlalchemy.dialects": [ - "exa.pyodbc = sqlalchemy_exasol.pyodbc:EXADialect_pyodbc", - "exa.turbodbc = sqlalchemy_exasol.turbodbc:EXADialect_turbodbc", - ] - }, -) diff --git a/sqlalchemy_exasol/__init__.py b/sqlalchemy_exasol/__init__.py index cec05359..3ab33b26 100644 --- a/sqlalchemy_exasol/__init__.py +++ b/sqlalchemy_exasol/__init__.py @@ -1,10 +1,8 @@ -try: - from ._version import version as __version__ -except ImportError: # pragma: no cover - __version__ = "unknown" - +from sqlalchemy_exasol.version import VERSION from sqlalchemy_exasol import base, pyodbc +__version__ = VERSION + # default dialect base.dialect = pyodbc.dialect diff --git a/sqlalchemy_exasol/pyodbc.py b/sqlalchemy_exasol/pyodbc.py index e5af6470..7507a1f4 100644 --- a/sqlalchemy_exasol/pyodbc.py +++ b/sqlalchemy_exasol/pyodbc.py @@ -9,7 +9,7 @@ import re import sys import logging -from distutils.version import LooseVersion +from packaging import version from sqlalchemy import sql from sqlalchemy.engine import reflection @@ -33,7 +33,7 @@ def __init__(self, **kw): def get_driver_version(self, connection): # LooseVersion will also work with interim versions like '4.2.7dev1' or '5.0.rc4' if self.driver_version is None: - self.driver_version = LooseVersion( + self.driver_version = version.parse( connection.connection.getinfo(self.dbapi.SQL_DRIVER_VER) or "2.0.0" ) return self.driver_version @@ -41,7 +41,7 @@ def get_driver_version(self, connection): def _get_server_version_info(self, connection): if self.server_version_info is None: # need to check if current version of EXAODBC returns proper server version - if self.get_driver_version(connection) >= LooseVersion("4.2.1"): + if self.get_driver_version(connection) >= version.parse("4.2.1"): # v4.2.1 and above should deliver usable SQL_DBMS_VER result = connection.connection.getinfo(self.dbapi.SQL_DBMS_VER).split( "." diff --git a/sqlalchemy_exasol/version.py b/sqlalchemy_exasol/version.py new file mode 100644 index 00000000..4ad95621 --- /dev/null +++ b/sqlalchemy_exasol/version.py @@ -0,0 +1,9 @@ +# ATTENTION: +# This file is generated, do not edit it manually! +# If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. + +MAJOR = 3 +MINOR = 0 +PATCH = 0 + +VERSION = f"{MAJOR}.{MINOR}.{PATCH}" diff --git a/test/test_exadialect_pyodbc.py b/test/test_exadialect_pyodbc.py index 3da6fd9d..469ee191 100644 --- a/test/test_exadialect_pyodbc.py +++ b/test/test_exadialect_pyodbc.py @@ -1,6 +1,6 @@ import pyodbc import pytest -from mock import Mock +from unittest.mock import Mock from sqlalchemy import testing from sqlalchemy.engine import url as sa_url from sqlalchemy.pool import _ConnectionFairy diff --git a/versioneer.py b/versioneer.py deleted file mode 100644 index 112b2b9f..00000000 --- a/versioneer.py +++ /dev/null @@ -1,809 +0,0 @@ - -# Version: 0.10 - -""" -## Installation - -First, decide on values for the following configuration variables: - -* `versionfile_source`: - - A project-relative pathname into which the generated version strings should - be written. This is usually a `_version.py` next to your project's main - `__init__.py` file. If your project uses `src/myproject/__init__.py`, this - should be `src/myproject/_version.py`. This file should be checked in to - your VCS as usual: the copy created below by `setup.py versioneer` will - include code that parses expanded VCS keywords in generated tarballs. The - 'build' and 'sdist' commands will replace it with a copy that has just the - calculated version string. - -* `versionfile_build`: - - Like `versionfile_source`, but relative to the build directory instead of - the source directory. These will differ when your setup.py uses - 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, - then you will probably have `versionfile_build='myproject/_version.py'` and - `versionfile_source='src/myproject/_version.py'`. - -* `tag_prefix`: - - a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. - If your tags look like 'myproject-1.2.0', then you should use - tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this - should be an empty string. - -* `parentdir_prefix`: - - a string, frequently the same as tag_prefix, which appears at the start of - all unpacked tarball filenames. If your tarball unpacks into - 'myproject-1.2.0', this should be 'myproject-'. - -This tool provides one script, named `versioneer-installer`. That script does -one thing: write a copy of `versioneer.py` into the current directory. - -To versioneer-enable your project: - -* 1: Run `versioneer-installer` to copy `versioneer.py` into the top of your - source tree. - -* 2: add the following lines to the top of your `setup.py`, with the - configuration values you decided earlier: - - import versioneer - versioneer.versionfile_source = 'src/myproject/_version.py' - versioneer.versionfile_build = 'myproject/_version.py' - versioneer.tag_prefix = '' # tags are like 1.2.0 - versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0' - -* 3: add the following arguments to the setup() call in your setup.py: - - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), - -* 4: now run `setup.py versioneer`, which will create `_version.py`, and - will modify your `__init__.py` to define `__version__` (by calling a - function from `_version.py`). It will also modify your `MANIFEST.in` to - include both `versioneer.py` and the generated `_version.py` in sdist - tarballs. - -* 5: commit these changes to your VCS. To make sure you won't forget, - `setup.py versioneer` will mark everything it touched for addition. - -## Post-Installation Usage - -Once established, all uses of your tree from a VCS checkout should get the -current version string. All generated tarballs should include an embedded -version string (so users who unpack them will not need a VCS tool installed). - -If you distribute your project through PyPI, then the release process should -boil down to two steps: - -* 1: git tag 1.0 -* 2: python setup.py register sdist upload - -If you distribute it through github (i.e. users use github to generate -tarballs with `git archive`), the process is: - -* 1: git tag 1.0 -* 2: git push; git push --tags - -Currently, all version strings must be based upon a tag. Versioneer will -report "unknown" until your tree has at least one tag in its history. This -restriction will be fixed eventually (see issue #12). - -## Version-String Flavors - -Code which uses Versioneer can learn about its version string at runtime by -importing `_version` from your main `__init__.py` file and running the -`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can -import the top-level `versioneer.py` and run `get_versions()`. - -Both functions return a dictionary with different keys for different flavors -of the version string: - -* `['version']`: condensed tag+distance+shortid+dirty identifier. For git, - this uses the output of `git describe --tags --dirty --always` but strips - the tag_prefix. For example "0.11-2-g1076c97-dirty" indicates that the tree - is like the "1076c97" commit but has uncommitted changes ("-dirty"), and - that this commit is two revisions ("-2-") beyond the "0.11" tag. For - released software (exactly equal to a known tag), the identifier will only - contain the stripped tag, e.g. "0.11". - -* `['full']`: detailed revision identifier. For Git, this is the full SHA1 - commit id, followed by "-dirty" if the tree contains uncommitted changes, - e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac-dirty". - -Some variants are more useful than others. Including `full` in a bug report -should allow developers to reconstruct the exact code being tested (or -indicate the presence of local changes that should be shared with the -developers). `version` is suitable for display in an "about" box or a CLI -`--version` output: it can be easily compared against release notes and lists -of bugs fixed in various releases. - -In the future, this will also include a -[PEP-0440](http://legacy.python.org/dev/peps/pep-0440/) -compatible flavor -(e.g. `1.2.post0.dev123`). This loses a lot of information (and has no room -for a hash-based revision id), but is safe to use in a `setup.py` -"`version=`" argument. It also enables tools like *pip* to compare version -strings and evaluate compatibility constraint declarations. - -The `setup.py versioneer` command adds the following text to your -`__init__.py` to place a basic version in `YOURPROJECT.__version__`: - - from ._version import get_versions - __version = get_versions()['version'] - del get_versions - -## Updating Versioneer - -To upgrade your project to a new release of Versioneer, do the following: - -* install the new Versioneer (`pip install -U versioneer` or equivalent) -* re-run `versioneer-installer` in your source tree to replace `versioneer.py` -* edit `setup.py`, if necessary, to include any new configuration settings indicated by the release notes -* re-run `setup.py versioneer` to replace `SRC/_version.py` -* commit any changed files - -## Future Directions - -This tool is designed to make it easily extended to other version-control -systems: all VCS-specific components are in separate directories like -src/git/ . The top-level `versioneer.py` script is assembled from these -components by running make-versioneer.py . In the future, make-versioneer.py -will take a VCS name as an argument, and will construct a version of -`versioneer.py` that is specific to the given VCS. It might also take the -configuration arguments that are currently provided manually during -installation by editing setup.py . Alternatively, it might go the other -direction and include code from all supported VCS systems, reducing the -number of intermediate scripts. - - -## License - -To make Versioneer easier to embed, all its code is hereby released into the -public domain. The `_version.py` that it creates is also in the public -domain. - -""" - -import os, sys, re -from distutils.core import Command -from distutils.command.sdist import sdist as _sdist -from distutils.command.build import build as _build - -versionfile_source = None -versionfile_build = None -tag_prefix = None -parentdir_prefix = None - -VCS = "git" - - -LONG_VERSION_PY = ''' -# This file helps to compute a version number in source trees obtained from -# git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build -# directories (produced by setup.py build) will contain a much shorter file -# that just contains the computed version number. - -# This file is released into the public domain. Generated by -# versioneer-0.10 (https://github.com/warner/python-versioneer) - -# these strings will be replaced by git during git-archive -git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" -git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" - - -import subprocess -import sys -import errno - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): - assert isinstance(commands, list) - p = None - for c in commands: - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except EnvironmentError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %%s" %% args[0]) - print(e) - return None - else: - if verbose: - print("unable to find command, tried %%s" %% (commands,)) - return None - stdout = p.communicate()[0].strip() - if sys.version >= '3': - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %%s (error)" %% args[0]) - return None - return stdout - - -import sys -import re -import os.path - -def get_expanded_variables(versionfile_abs): - # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} - try: - f = open(versionfile_abs,"r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - variables["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - variables["full"] = mo.group(1) - f.close() - except EnvironmentError: - pass - return variables - -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("variables are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %%d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) - if verbose: - print("discarding '%%s', no digits" %% ",".join(refs-tags)) - if verbose: - print("likely tags: %%s" %% ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - if verbose: - print("picking %%s" %% r) - return { "version": r, - "full": variables["full"].strip() } - # no suitable tags, so we use the full revision id - if verbose: - print("no suitable tags, using full revision id") - return { "version": variables["full"].strip(), - "full": variables["full"].strip() } - -def versions_from_vcs(tag_prefix, root, verbose=False): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' variables were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. - - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %%s" %% root) - return {} - - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def versions_from_parentdir(parentdir_prefix, root, verbose=False): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %% - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -tag_prefix = "%(TAG_PREFIX)s" -parentdir_prefix = "%(PARENTDIR_PREFIX)s" -versionfile_source = "%(VERSIONFILE_SOURCE)s" - -def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have - # __file__, we can work backwards from there to the root. Some - # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded variables. - - variables = { "refnames": git_refnames, "full": git_full } - ver = versions_from_expanded_variables(variables, tag_prefix, verbose) - if ver: - return ver - - try: - root = os.path.abspath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to this file. Invert - # this to find the root from __file__. - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - except NameError: - return default - - return (versions_from_vcs(tag_prefix, root, verbose) - or versions_from_parentdir(parentdir_prefix, root, verbose) - or default) - -''' - - -import subprocess -import sys -import errno - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): - assert isinstance(commands, list) - p = None - for c in commands: - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except EnvironmentError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %s" % args[0]) - print(e) - return None - else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None - stdout = p.communicate()[0].strip() - if sys.version >= '3': - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %s (error)" % args[0]) - return None - return stdout - - -import sys -import re -import os.path - -def get_expanded_variables(versionfile_abs): - # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} - try: - f = open(versionfile_abs,"r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - variables["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - variables["full"] = mo.group(1) - f.close() - except EnvironmentError: - pass - return variables - -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("variables are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) - if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) - if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - if verbose: - print("picking %s" % r) - return { "version": r, - "full": variables["full"].strip() } - # no suitable tags, so we use the full revision id - if verbose: - print("no suitable tags, using full revision id") - return { "version": variables["full"].strip(), - "full": variables["full"].strip() } - -def versions_from_vcs(tag_prefix, root, verbose=False): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' variables were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. - - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %s" % root) - return {} - - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def versions_from_parentdir(parentdir_prefix, root, verbose=False): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} -import os.path -import sys - -# os.path.relpath only appeared in Python-2.6 . Define it here for 2.5. -def os_path_relpath(path, start=os.path.curdir): - """Return a relative version of a path""" - - if not path: - raise ValueError("no path specified") - - start_list = [x for x in os.path.abspath(start).split(os.path.sep) if x] - path_list = [x for x in os.path.abspath(path).split(os.path.sep) if x] - - # Work out how much of the filepath is shared by start and path. - i = len(os.path.commonprefix([start_list, path_list])) - - rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return os.path.curdir - return os.path.join(*rel_list) - -def do_vcs_install(manifest_in, versionfile_source, ipy): - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - files = [manifest_in, versionfile_source, ipy] - try: - me = __file__ - if me.endswith(".pyc") or me.endswith(".pyo"): - me = os.path.splitext(me)[0] + ".py" - versioneer_file = os_path_relpath(me) - except NameError: - versioneer_file = "versioneer.py" - files.append(versioneer_file) - present = False - try: - f = open(".gitattributes", "r") - for line in f.readlines(): - if line.strip().startswith(versionfile_source): - if "export-subst" in line.strip().split()[1:]: - present = True - f.close() - except EnvironmentError: - pass - if not present: - f = open(".gitattributes", "a+") - f.write("%s export-subst\n" % versionfile_source) - f.close() - files.append(".gitattributes") - run_command(GITS, ["add", "--"] + files) - -SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.10) from -# revision-control system data, or from the parent directory name of an -# unpacked source archive. Distribution tarballs contain a pre-generated copy -# of this file. - -version_version = '%(version)s' -version_full = '%(full)s' -def get_versions(default={}, verbose=False): - return {'version': version_version, 'full': version_full} - -""" - -DEFAULT = {"version": "unknown", "full": "unknown"} - -def versions_from_file(filename): - versions = {} - try: - f = open(filename) - except EnvironmentError: - return versions - for line in f.readlines(): - mo = re.match("version_version = '([^']+)'", line) - if mo: - versions["version"] = mo.group(1) - mo = re.match("version_full = '([^']+)'", line) - if mo: - versions["full"] = mo.group(1) - f.close() - return versions - -def write_to_version_file(filename, versions): - f = open(filename, "w") - f.write(SHORT_VERSION_PY % versions) - f.close() - print("set %s to '%s'" % (filename, versions["version"])) - -def get_root(): - try: - return os.path.dirname(os.path.abspath(__file__)) - except NameError: - return os.path.dirname(os.path.abspath(sys.argv[0])) - -def get_versions(default=DEFAULT, verbose=False): - # returns dict with two keys: 'version' and 'full' - assert versionfile_source is not None, "please set versioneer.versionfile_source" - assert tag_prefix is not None, "please set versioneer.tag_prefix" - assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix" - # I am in versioneer.py, which must live at the top of the source tree, - # which we use to compute the root directory. py2exe/bbfreeze/non-CPython - # don't have __file__, in which case we fall back to sys.argv[0] (which - # ought to be the setup.py script). We prefer __file__ since that's more - # robust in cases where setup.py was invoked in some weird way (e.g. pip) - root = get_root() - versionfile_abs = os.path.join(root, versionfile_source) - - # extract version from first of _version.py, 'git describe', parentdir. - # This is meant to work for developers using a source checkout, for users - # of a tarball created by 'setup.py sdist', and for users of a - # tarball/zipball created by 'git archive' or github's download-from-tag - # feature. - - variables = get_expanded_variables(versionfile_abs) - if variables: - ver = versions_from_expanded_variables(variables, tag_prefix) - if ver: - if verbose: print("got version from expanded variable %s" % ver) - return ver - - ver = versions_from_file(versionfile_abs) - if ver: - if verbose: print("got version from file %s %s" % (versionfile_abs,ver)) - return ver - - ver = versions_from_vcs(tag_prefix, root, verbose) - if ver: - if verbose: print("got version from git %s" % ver) - return ver - - ver = versions_from_parentdir(parentdir_prefix, root, verbose) - if ver: - if verbose: print("got version from parentdir %s" % ver) - return ver - - if verbose: print("got version from default %s" % ver) - return default - -def get_version(verbose=False): - return get_versions(verbose=verbose)["version"] - -class cmd_version(Command): - description = "report generated version string" - user_options = [] - boolean_options = [] - def initialize_options(self): - pass - def finalize_options(self): - pass - def run(self): - ver = get_version(verbose=True) - print("Version is currently: %s" % ver) - - -class cmd_build(_build): - def run(self): - versions = get_versions(verbose=True) - _build.run(self) - # now locate _version.py in the new build/ directory and replace it - # with an updated value - target_versionfile = os.path.join(self.build_lib, versionfile_build) - print("UPDATING %s" % target_versionfile) - os.unlink(target_versionfile) - f = open(target_versionfile, "w") - f.write(SHORT_VERSION_PY % versions) - f.close() - -if 'cx_Freeze' in sys.modules: # cx_freeze enabled? - from cx_Freeze.dist import build_exe as _build_exe - - class cmd_build_exe(_build_exe): - def run(self): - versions = get_versions(verbose=True) - target_versionfile = versionfile_source - print("UPDATING %s" % target_versionfile) - os.unlink(target_versionfile) - f = open(target_versionfile, "w") - f.write(SHORT_VERSION_PY % versions) - f.close() - _build_exe.run(self) - os.unlink(target_versionfile) - f = open(versionfile_source, "w") - f.write(LONG_VERSION_PY % {"DOLLAR": "$", - "TAG_PREFIX": tag_prefix, - "PARENTDIR_PREFIX": parentdir_prefix, - "VERSIONFILE_SOURCE": versionfile_source, - }) - f.close() - -class cmd_sdist(_sdist): - def run(self): - versions = get_versions(verbose=True) - self._versioneer_generated_versions = versions - # unless we update this, the command will keep using the old version - self.distribution.metadata.version = versions["version"] - return _sdist.run(self) - - def make_release_tree(self, base_dir, files): - _sdist.make_release_tree(self, base_dir, files) - # now locate _version.py in the new base_dir directory (remembering - # that it may be a hardlink) and replace it with an updated value - target_versionfile = os.path.join(base_dir, versionfile_source) - print("UPDATING %s" % target_versionfile) - os.unlink(target_versionfile) - f = open(target_versionfile, "w") - f.write(SHORT_VERSION_PY % self._versioneer_generated_versions) - f.close() - -INIT_PY_SNIPPET = """ -from ._version import get_versions -__version__ = get_versions()['version'] -del get_versions -""" - -class cmd_update_files(Command): - description = "install/upgrade Versioneer files: __init__.py SRC/_version.py" - user_options = [] - boolean_options = [] - def initialize_options(self): - pass - def finalize_options(self): - pass - def run(self): - print(" creating %s" % versionfile_source) - f = open(versionfile_source, "w") - f.write(LONG_VERSION_PY % {"DOLLAR": "$", - "TAG_PREFIX": tag_prefix, - "PARENTDIR_PREFIX": parentdir_prefix, - "VERSIONFILE_SOURCE": versionfile_source, - }) - f.close() - - ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py") - try: - old = open(ipy, "r").read() - except EnvironmentError: - old = "" - if INIT_PY_SNIPPET not in old: - print(" appending to %s" % ipy) - f = open(ipy, "a") - f.write(INIT_PY_SNIPPET) - f.close() - else: - print(" %s unmodified" % ipy) - - # Make sure both the top-level "versioneer.py" and versionfile_source - # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so - # they'll be copied into source distributions. Pip won't be able to - # install the package without this. - manifest_in = os.path.join(get_root(), "MANIFEST.in") - simple_includes = set() - try: - for line in open(manifest_in, "r").readlines(): - if line.startswith("include "): - for include in line.split()[1:]: - simple_includes.add(include) - except EnvironmentError: - pass - # That doesn't cover everything MANIFEST.in can do - # (http://docs.python.org/2/distutils/sourcedist.html#commands), so - # it might give some false negatives. Appending redundant 'include' - # lines is safe, though. - if "versioneer.py" not in simple_includes: - print(" appending 'versioneer.py' to MANIFEST.in") - f = open(manifest_in, "a") - f.write("include versioneer.py\n") - f.close() - else: - print(" 'versioneer.py' already in MANIFEST.in") - if versionfile_source not in simple_includes: - print(" appending versionfile_source ('%s') to MANIFEST.in" % - versionfile_source) - f = open(manifest_in, "a") - f.write("include %s\n" % versionfile_source) - f.close() - else: - print(" versionfile_source already in MANIFEST.in") - - # Make VCS-specific changes. For git, this means creating/changing - # .gitattributes to mark _version.py for export-time keyword - # substitution. - do_vcs_install(manifest_in, versionfile_source, ipy) - -def get_cmdclass(): - cmds = {'version': cmd_version, - 'versioneer': cmd_update_files, - 'build': cmd_build, - 'sdist': cmd_sdist, - } - if 'cx_Freeze' in sys.modules: # cx_freeze enabled? - cmds['build_exe'] = cmd_build_exe - del cmds['build'] - - return cmds