From b7239e93c845dedb8a0d16e125b6a736a7f1283b Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Tue, 10 Nov 2020 15:59:23 +0100 Subject: [PATCH] Refactor project (#1) * feat: update project --- .github/ISSUE_TEMPLATE/bug_report.md | 38 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/PULL_REQUEST_TEMPLATE.md | 5 + .github/workflows/python-package.yml | 48 + .gitignore | 1 + .travis.yml | 88 +- CONTRIBUTING.md | 59 +- Pipfile | 13 + Pipfile.lock | 867 ++++++++++++++++++ bootstrap | 84 -- consulate.iml | 10 - consulate/__init__.py | 51 +- consulate/adapters.py | 42 +- consulate/api/__init__.py | 17 +- consulate/api/acl.py | 236 +++-- consulate/api/agent.py | 161 ++-- consulate/api/base.py | 69 +- consulate/api/catalog.py | 45 +- consulate/api/coordinate.py | 50 +- consulate/api/event.py | 28 +- consulate/api/health.py | 17 +- consulate/api/kv.py | 111 ++- consulate/api/lock.py | 22 +- consulate/api/session.py | 32 +- consulate/api/status.py | 4 +- consulate/cli.py | 452 +++++---- consulate/client.py | 65 +- consulate/models/acl.py | 215 +++-- consulate/models/agent.py | 239 ++--- consulate/models/base.py | 55 +- consulate/utils.py | 28 +- docker-compose.yml | 17 +- docs/conf.py | 20 +- pylintrc | 467 ++++++++++ pyproject.toml | 16 + requires/installation.txt | 1 - requires/optional.txt | 1 - requires/testing.txt | 10 - setup.cfg | 29 +- setup.py | 98 +- tests/__init__.py | 14 - tests/base.py | 28 +- tests/base_model_tests.py | 139 --- tests/catalog_tests.py | 11 - tests/conftest.py | 38 + tests/consul.json | 16 + tests/event_tests.py | 18 - tests/{acl_tests.py => test_acl.py} | 108 +-- tests/{agent_tests.py => test_agent.py} | 172 ++-- tests/{api_tests.py => test_api.py} | 233 ++--- tests/test_base_model.py | 104 +++ tests/test_catalog.py | 9 + ...coordinate_tests.py => test_coordinate.py} | 1 + tests/test_event.py | 12 + tests/{kv_tests.py => test_kv.py} | 187 ++-- tests/{lock_tests.py => test_lock.py} | 0 tests/{session_tests.py => test_session.py} | 17 +- tests/test_utils.py | 70 ++ tests/utils_tests.py | 47 +- 59 files changed, 3045 insertions(+), 2010 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/python-package.yml create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100755 bootstrap delete mode 100644 consulate.iml create mode 100644 pylintrc create mode 100644 pyproject.toml delete mode 100644 requires/installation.txt delete mode 100644 requires/optional.txt delete mode 100644 requires/testing.txt delete mode 100644 tests/base_model_tests.py delete mode 100644 tests/catalog_tests.py create mode 100644 tests/conftest.py create mode 100644 tests/consul.json delete mode 100644 tests/event_tests.py rename tests/{acl_tests.py => test_acl.py} (73%) rename tests/{agent_tests.py => test_agent.py} (56%) rename tests/{api_tests.py => test_api.py} (53%) create mode 100644 tests/test_base_model.py create mode 100644 tests/test_catalog.py rename tests/{coordinate_tests.py => test_coordinate.py} (99%) create mode 100644 tests/test_event.py rename tests/{kv_tests.py => test_kv.py} (57%) rename tests/{lock_tests.py => test_lock.py} (100%) rename tests/{session_tests.py => test_session.py} (61%) create mode 100644 tests/test_utils.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..80e5f01 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +Fixes # [refer the issue number] + +Changes proposed in this PR: +- [Updated X functionality] +- [Refactor X class] diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..61b5ce8 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,48 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pipenv + pipenv install --dev --system + - name: Test with pytest + run: | + python setup.py test + - name: Run mypy + continue-on-error: true + run: | + mypy consulate + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 consulate --show-source --statistics + - name: Lint with pylint + run: | + pylint --rcfile=pylintrc consulate + - name: Security safety + run: | + safety check + - name: Security bandit + run: | + bandit -r consulate/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7098fa4..f03dd54 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ cover docs/_build MANIFEST venv +htmlcov \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 8737bad..a99bc8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,78 +1,18 @@ -sudo: required - -services: - - docker - language: python - -env: - global: - - PATH=$HOME/.local/bin:$PATH - - AWS_DEFAULT_REGION=us-east-1 - - secure: "GxBrm8hKFLazshbB3c+rQUQYHAim/rm8KtBXz6uNWRvsccAK6otc5Wcz01kj679d9C9L3I/G6/wCxfgglQp3YMGKlLT+jHQpOMdDauEdeKvaUuPh3hDQBPSAUhIpycgxz9AghdWqOQGkpQQqNW9UZRC42SW2eIyWFIlQf6W31Vc=" - - secure: "YdDXZGAOp1WZenhfZnHhL9cOeUQbvZZOr8FWWJoJz3EdTbf6jE2UuK4HU6CgoYDpfFlc7geah2xARr/2rkN716Rz4ZzeHBtNXfwqqQRWRLkB8J73Nowmm+6J+skv6meJyCHtcHalpv7pAH5QPn3zMK2EAogdcDJmd5GSVBjwPoo=" - -stages: -- test -- name: upload coverage -- name: deploy - if: tag IS present - +python: + - "3.8" install: -- pip install awscli -- pip install -r requires/testing.txt -- python setup.py develop - -before_script: -- ./bootstrap -- source build/test-environment - -script: nosetests - + - python -m pip install --upgrade pip + - pip install --upgrade setuptools coveralls pipenv + - pipenv lock --dev --requirements > requirements.txt + - pip install -r requirements.txt +script: + - coverage erase + - python setup.py test after_success: - - aws s3 cp .coverage "s3://com-gavinroy-travis/consulate/$TRAVIS_BUILD_NUMBER/.coverage.${TRAVIS_PYTHON_VERSION}" + - coverage combine + - coveralls -jobs: - include: - - python: 2.7 - - python: 3.4 - - python: 3.5 - - python: 3.6 - - python: pypy - - python: pypy3 - - stage: upload coverage - if: repo IS gmr/consulate - sudo: false - services: [] - python: 3.6 - install: - - pip install awscli coverage codecov - before_script: true - script: - - mkdir coverage - - aws s3 cp --recursive s3://com-gavinroy-travis/consulate/$TRAVIS_BUILD_NUMBER/ - coverage - - cd coverage - - coverage combine - - cd .. - - mv coverage/.coverage . - - coverage report - after_success: codecov - - stage: deploy - sudo: false - if: repo IS gmr/consulate - python: 3.6 - services: [] - install: true - before_script: true - script: true - after_success: true - deploy: - distributions: sdist bdist_wheel - provider: pypi - user: crad - on: - tags: true - all_branches: true - password: - secure: "W3mHi2tzX34KcN82tdVPZwS0RHBGkI2Cy/df18ehsVKoOzWzQvaQKh2Jh1LIBOvdZCyih+KltmMNT8ounqUd+ql6kBFd0kotQk3C3x5R5KlpbhQ4BNKp++grs+iycPwK6qnJISypt2d3ykJNgqUgfBI0p+7XVpMkBY0GIXGNcvk=" +notifications: + email: + - a.vara.1986@gmail.com \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a208ba9..2db739a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,60 +1,3 @@ # Contributing -## Test Coverage - -To contribute to Consulate, please make sure that any new features or changes -to existing functionality **include test coverage**. - -*Pull requests that add or change code without coverage have a much lower chance -of being accepted.* - -## Prerequisites - -Consulate test suite has a couple of requirements: - - * Dependencies from [requires/testing.txt](requires/testing.txt) are installed - * Local Docker and [docker-compose](https://docs.docker.com/compose/) - -## Installing Dependencies - -You may want to develop in a virtual environment. This is usually done inside the source -repository, and `.gitignore` is configured to ignore a virtual environment in `env`. - -```bash -python3 -m venv env -source env/bin/activate -``` - -To install the dependencies needed to run Consulate tests, use - -```bash -pip install -r requires/testing.txt -``` - -## Starting the test dependency - -Prior to running tests, ensure that Consul is running via Docker using: - -```bash -./bootstrap -``` - -This script uses [docker-compose](https://docs.docker.com/compose/) to launch a Consul server container that is -pre-configured for the tests. In addition, it configures `build/test-environment` that is loaded -by the tests with configuration information for connecting to Consul. - -## Running Tests - -To run all test suites, run: - - nosetests - -## Code Formatting - -Please format your code using [yapf](http://pypi.python.org/pypi/yapf) -with ``pep8`` style prior to issuing your pull request. In addition, run -``flake8`` to look for any style errors prior to submitting your PR. - -Both are included when the test requirements are installed. If you are fixing -formatting for existing code, please separate code-reformatting commits from -functionality changes. +See this [webpage](https://python-microservices.github.io/contributing/) \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..291b367 --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +py-ms-consulate = {editable = true,extras = ["tests"],path = "."} + +[packages] +py-ms-consulate = {editable = true, extras = ["unixsocket"],path = "."} + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..49a0f26 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,867 @@ +{ + "_meta": { + "hash": { + "sha256": "aa073905b730471a6b3471c49d595c8e70546d81f91eb5122bbe238057f8a8c4" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd", + "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4" + ], + "version": "==2020.11.8" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "idna": { + "hashes": [ + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.10" + }, + "py-ms-consulate": { + "editable": true, + "extras": [ + "unixsocket" + ], + "path": "." + }, + "pytest-runner": { + "hashes": [ + "sha256:5534b08b133ef9a5e2c22c7886a8f8508c95bb0b0bdc6cc13214f269c3c70d51", + "sha256:96c7e73ead7b93e388c5d614770d2bae6526efd997757d3543fe17b557a0942b" + ], + "markers": "python_version >= '2.7'", + "version": "==5.2" + }, + "requests": { + "hashes": [ + "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", + "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.24.0" + }, + "requests-unixsocket": { + "hashes": [ + "sha256:014d07bfb66dc805a011a8b4b306cf4ec96d2eddb589f6b2b5765e626f0dc0cc", + "sha256:9e5c1a20afc3cf786197ae59c79bcdb0e7565f218f27df5f891307ee8817c1ea" + ], + "version": "==0.2.0" + }, + "urllib3": { + "hashes": [ + "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", + "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.25.11" + } + }, + "develop": { + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "astroid": { + "hashes": [ + "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703", + "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386" + ], + "markers": "python_version >= '3.5'", + "version": "==2.4.2" + }, + "attrs": { + "hashes": [ + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.3.0" + }, + "bandit": { + "hashes": [ + "sha256:336620e220cf2d3115877685e264477ff9d9abaeb0afe3dc7264f55fa17a3952", + "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065" + ], + "version": "==1.6.2" + }, + "bcrypt": { + "hashes": [ + "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", + "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", + "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", + "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", + "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", + "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", + "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.0" + }, + "black": { + "hashes": [ + "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea" + ], + "markers": "python_version >= '3.6'", + "version": "==20.8b1" + }, + "cached-property": { + "hashes": [ + "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130", + "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0" + ], + "version": "==1.5.2" + }, + "certifi": { + "hashes": [ + "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd", + "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4" + ], + "version": "==2020.11.8" + }, + "cffi": { + "hashes": [ + "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d", + "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b", + "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4", + "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f", + "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3", + "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579", + "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537", + "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e", + "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05", + "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171", + "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca", + "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522", + "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c", + "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc", + "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d", + "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808", + "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828", + "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869", + "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d", + "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9", + "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0", + "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc", + "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15", + "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c", + "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a", + "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3", + "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1", + "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768", + "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d", + "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b", + "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e", + "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d", + "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730", + "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394", + "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1", + "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591" + ], + "version": "==1.14.3" + }, + "cfgv": { + "hashes": [ + "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d", + "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1" + ], + "markers": "python_full_version >= '3.6.1'", + "version": "==3.2.0" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==7.1.2" + }, + "coverage": { + "hashes": [ + "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516", + "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259", + "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9", + "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097", + "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0", + "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f", + "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7", + "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c", + "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5", + "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7", + "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729", + "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978", + "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9", + "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f", + "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9", + "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822", + "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418", + "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82", + "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f", + "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d", + "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221", + "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4", + "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21", + "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709", + "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54", + "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d", + "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270", + "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24", + "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751", + "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a", + "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237", + "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7", + "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636", + "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==5.3" + }, + "cryptography": { + "hashes": [ + "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538", + "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f", + "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77", + "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b", + "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33", + "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e", + "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb", + "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e", + "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7", + "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297", + "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d", + "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7", + "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b", + "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7", + "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4", + "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8", + "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b", + "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851", + "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13", + "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b", + "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3", + "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.2.1" + }, + "distlib": { + "hashes": [ + "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", + "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" + ], + "version": "==0.3.1" + }, + "distro": { + "hashes": [ + "sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92", + "sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799" + ], + "version": "==1.5.0" + }, + "docker": { + "extras": [ + "ssh" + ], + "hashes": [ + "sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828", + "sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.3.1" + }, + "docker-compose": { + "hashes": [ + "sha256:5a5690f24c27d4b43dcbe6b3fae91ba680713208e99ee863352b3bae37bcaa83", + "sha256:84ca2edad226435e3a378ea24ca2ca4e1a77cc7c8de057e2812124c6dcb55147" + ], + "markers": "python_version >= '3.4'", + "version": "==1.27.4" + }, + "dockerpty": { + "hashes": [ + "sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce" + ], + "version": "==0.4.1" + }, + "docopt": { + "hashes": [ + "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" + ], + "version": "==0.6.2" + }, + "dparse": { + "hashes": [ + "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367", + "sha256:e953a25e44ebb60a5c6efc2add4420c177f1d8404509da88da9729202f306994" + ], + "markers": "python_version >= '3.5'", + "version": "==0.5.1" + }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" + }, + "flake8": { + "hashes": [ + "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", + "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==3.8.4" + }, + "gitdb": { + "hashes": [ + "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", + "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" + ], + "markers": "python_version >= '3.4'", + "version": "==4.0.5" + }, + "gitpython": { + "hashes": [ + "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b", + "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8" + ], + "markers": "python_version >= '3.4'", + "version": "==3.1.11" + }, + "httmock": { + "hashes": [ + "sha256:13e6c63f135a928e15d386af789a2890efb03e0e280f29bdc9961f3f0dc34cb9", + "sha256:44eaf4bb59cc64cd6f5d8bf8700b46aa3097cc5651b9bc85c527dfbc71792f41" + ], + "version": "==1.4.0" + }, + "identify": { + "hashes": [ + "sha256:5dd84ac64a9a115b8e0b27d1756b244b882ad264c3c423f42af8235a6e71ca12", + "sha256:c9504ba6a043ee2db0a9d69e43246bc138034895f6338d5aed1b41e4a73b1513" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.5.9" + }, + "idna": { + "hashes": [ + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.10" + }, + "importlib-metadata": { + "hashes": [ + "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da", + "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3" + ], + "markers": "python_version < '3.8' and python_version < '3.8'", + "version": "==2.0.0" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "isort": { + "hashes": [ + "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7", + "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58" + ], + "markers": "python_version >= '3.6' and python_version < '4.0'", + "version": "==5.6.4" + }, + "jsonschema": { + "hashes": [ + "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", + "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" + ], + "version": "==3.2.0" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", + "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", + "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", + "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", + "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", + "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", + "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", + "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", + "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", + "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", + "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", + "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", + "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", + "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", + "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", + "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", + "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", + "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", + "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", + "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", + "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.4.3" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "mypy": { + "hashes": [ + "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324", + "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc", + "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802", + "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122", + "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975", + "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7", + "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666", + "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669", + "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178", + "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01", + "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea", + "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de", + "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1", + "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c" + ], + "markers": "python_version >= '3.5'", + "version": "==0.790" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "nodeenv": { + "hashes": [ + "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9", + "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c" + ], + "version": "==1.5.0" + }, + "packaging": { + "hashes": [ + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.4" + }, + "paramiko": { + "hashes": [ + "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898", + "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035" + ], + "version": "==2.7.2" + }, + "pathspec": { + "hashes": [ + "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", + "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d" + ], + "version": "==0.8.1" + }, + "pbr": { + "hashes": [ + "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9", + "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00" + ], + "markers": "python_version >= '2.6'", + "version": "==5.5.1" + }, + "pluggy": { + "hashes": [ + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" + }, + "pre-commit": { + "hashes": [ + "sha256:22e6aa3bd571debb01eb7d34483f11c01b65237be4eebbf30c3d4fb65762d315", + "sha256:905ebc9b534b991baec87e934431f2d0606ba27f2b90f7f652985f5a5b8b6ae6" + ], + "markers": "python_full_version >= '3.6.1'", + "version": "==2.8.2" + }, + "py": { + "hashes": [ + "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", + "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.9.0" + }, + "py-ms-consulate": { + "editable": true, + "extras": [ + "unixsocket" + ], + "path": "." + }, + "pycodestyle": { + "hashes": [ + "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", + "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.6.0" + }, + "pycparser": { + "hashes": [ + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.20" + }, + "pyflakes": { + "hashes": [ + "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", + "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.2.0" + }, + "pylint": { + "hashes": [ + "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210", + "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f" + ], + "markers": "python_version >= '3.5'", + "version": "==2.6.0" + }, + "pynacl": { + "hashes": [ + "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4", + "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4", + "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574", + "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d", + "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634", + "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25", + "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f", + "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505", + "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122", + "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7", + "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420", + "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f", + "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96", + "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6", + "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6", + "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514", + "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff", + "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.4.0" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.4.7" + }, + "pyrsistent": { + "hashes": [ + "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" + ], + "markers": "python_version >= '3.5'", + "version": "==0.17.3" + }, + "pytest": { + "hashes": [ + "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe", + "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e" + ], + "markers": "python_version >= '3.5'", + "version": "==6.1.2" + }, + "pytest-cov": { + "hashes": [ + "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191", + "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.10.1" + }, + "pytest-docker": { + "hashes": [ + "sha256:7a0544dc00c83fd7808907a55835b1b7c07e3ea2d22ee558b5a233247e462735", + "sha256:9faa9c87e6e0920612005c8d187c58b3d336f3af49a878f358578de125963858" + ], + "markers": "python_version >= '3.6'", + "version": "==0.10.1" + }, + "pytest-runner": { + "hashes": [ + "sha256:5534b08b133ef9a5e2c22c7886a8f8508c95bb0b0bdc6cc13214f269c3c70d51", + "sha256:96c7e73ead7b93e388c5d614770d2bae6526efd997757d3543fe17b557a0942b" + ], + "markers": "python_version >= '2.7'", + "version": "==5.2" + }, + "python-dotenv": { + "hashes": [ + "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e", + "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0" + ], + "version": "==0.15.0" + }, + "pyyaml": { + "hashes": [ + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" + ], + "version": "==5.3.1" + }, + "regex": { + "hashes": [ + "sha256:03855ee22980c3e4863dc84c42d6d2901133362db5daf4c36b710dd895d78f0a", + "sha256:06b52815d4ad38d6524666e0d50fe9173533c9cc145a5779b89733284e6f688f", + "sha256:11116d424734fe356d8777f89d625f0df783251ada95d6261b4c36ad27a394bb", + "sha256:119e0355dbdd4cf593b17f2fc5dbd4aec2b8899d0057e4957ba92f941f704bf5", + "sha256:127a9e0c0d91af572fbb9e56d00a504dbd4c65e574ddda3d45b55722462210de", + "sha256:1ec66700a10e3c75f1f92cbde36cca0d3aaee4c73dfa26699495a3a30b09093c", + "sha256:227a8d2e5282c2b8346e7f68aa759e0331a0b4a890b55a5cfbb28bd0261b84c0", + "sha256:2564def9ce0710d510b1fc7e5178ce2d20f75571f788b5197b3c8134c366f50c", + "sha256:297116e79074ec2a2f885d22db00ce6e88b15f75162c5e8b38f66ea734e73c64", + "sha256:2dc522e25e57e88b4980d2bdd334825dbf6fa55f28a922fc3bfa60cc09e5ef53", + "sha256:3a5f08039eee9ea195a89e180c5762bfb55258bfb9abb61a20d3abee3b37fd12", + "sha256:3dfca201fa6b326239e1bccb00b915e058707028809b8ecc0cf6819ad233a740", + "sha256:49461446b783945597c4076aea3f49aee4b4ce922bd241e4fcf62a3e7c61794c", + "sha256:4afa350f162551cf402bfa3cd8302165c8e03e689c897d185f16a167328cc6dd", + "sha256:4b5a9bcb56cc146c3932c648603b24514447eafa6ce9295234767bf92f69b504", + "sha256:52e83a5f28acd621ba8e71c2b816f6541af7144b69cc5859d17da76c436a5427", + "sha256:625116aca6c4b57c56ea3d70369cacc4d62fead4930f8329d242e4fe7a58ce4b", + "sha256:654c1635f2313d0843028487db2191530bca45af61ca85d0b16555c399625b0e", + "sha256:8092a5a06ad9a7a247f2a76ace121183dc4e1a84c259cf9c2ce3bbb69fac3582", + "sha256:832339223b9ce56b7b15168e691ae654d345ac1635eeb367ade9ecfe0e66bee0", + "sha256:8ca9dca965bd86ea3631b975d63b0693566d3cc347e55786d5514988b6f5b84c", + "sha256:96f99219dddb33e235a37283306834700b63170d7bb2a1ee17e41c6d589c8eb9", + "sha256:9b6305295b6591e45f069d3553c54d50cc47629eb5c218aac99e0f7fafbf90a1", + "sha256:a62162be05edf64f819925ea88d09d18b09bebf20971b363ce0c24e8b4aa14c0", + "sha256:aacc8623ffe7999a97935eeabbd24b1ae701d08ea8f874a6ff050e93c3e658cf", + "sha256:b45bab9f224de276b7bc916f6306b86283f6aa8afe7ed4133423efb42015a898", + "sha256:b88fa3b8a3469f22b4f13d045d9bd3eda797aa4e406fde0a2644bc92bbdd4bdd", + "sha256:b8a686a6c98872007aa41fdbb2e86dc03b287d951ff4a7f1da77fb7f14113e4d", + "sha256:bd904c0dec29bbd0769887a816657491721d5f545c29e30fd9d7a1a275dc80ab", + "sha256:bf4f896c42c63d1f22039ad57de2644c72587756c0cfb3cc3b7530cfe228277f", + "sha256:c13d311a4c4a8d671f5860317eb5f09591fbe8259676b86a85769423b544451e", + "sha256:c2c6c56ee97485a127555c9595c069201b5161de9d05495fbe2132b5ac104786", + "sha256:c32c91a0f1ac779cbd73e62430de3d3502bbc45ffe5bb6c376015acfa848144b", + "sha256:c3466a84fce42c2016113101018a9981804097bacbab029c2d5b4fcb224b89de", + "sha256:c454ad88e56e80e44f824ef8366bb7e4c3def12999151fd5c0ea76a18fe9aa3e", + "sha256:c8a2b7ccff330ae4c460aff36626f911f918555660cc28163417cb84ffb25789", + "sha256:cb905f3d2e290a8b8f1579d3984f2cfa7c3a29cc7cba608540ceeed18513f520", + "sha256:cfcf28ed4ce9ced47b9b9670a4f0d3d3c0e4d4779ad4dadb1ad468b097f808aa", + "sha256:dd3e6547ecf842a29cf25123fbf8d2461c53c8d37aa20d87ecee130c89b7079b", + "sha256:de7fd57765398d141949946c84f3590a68cf5887dac3fc52388df0639b01eda4", + "sha256:ea37320877d56a7f0a1e6a625d892cf963aa7f570013499f5b8d5ab8402b5625", + "sha256:f1fce1e4929157b2afeb4bb7069204d4370bab9f4fc03ca1fbec8bd601f8c87d", + "sha256:f43109822df2d3faac7aad79613f5f02e4eab0fc8ad7932d2e70e2a83bd49c26" + ], + "version": "==2020.10.28" + }, + "requests": { + "hashes": [ + "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", + "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.24.0" + }, + "requests-mock": { + "hashes": [ + "sha256:11215c6f4df72702aa357f205cf1e537cffd7392b3e787b58239bde5fb3db53b", + "sha256:e68f46844e4cee9d447150343c9ae875f99fa8037c6dcf5f15bf1fe9ab43d226" + ], + "version": "==1.8.0" + }, + "safety": { + "hashes": [ + "sha256:23bf20690d4400edc795836b0c983c2b4cbbb922233108ff925b7dd7750f00c9", + "sha256:86c1c4a031fe35bd624fce143fbe642a0234d29f7cbf7a9aa269f244a955b087" + ], + "markers": "python_version >= '3.5'", + "version": "==1.9.0" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.15.0" + }, + "smmap": { + "hashes": [ + "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", + "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==3.0.4" + }, + "stevedore": { + "hashes": [ + "sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62", + "sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.2" + }, + "texttable": { + "hashes": [ + "sha256:ce0faf21aa77d806bbff22b107cc22cce68dc9438f97a2df32c93e9afa4ce436", + "sha256:f802f2ef8459058736264210f716c757cbf85007a30886d8541aa8c3404f1dda" + ], + "version": "==1.6.3" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" + }, + "tox": { + "hashes": [ + "sha256:42ce19ce5dc2f6d6b1fdc5666c476e1f1e2897359b47e0aa3a5b774f335d57c2", + "sha256:4321052bfe28f9d85082341ca8e233e3ea901fdd14dab8a5d3fbd810269fbaf6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.20.1" + }, + "typed-ast": { + "hashes": [ + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072", + "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298", + "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "markers": "implementation_name == 'cpython' and python_version < '3.8'", + "version": "==1.4.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", + "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", + "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + ], + "version": "==3.7.4.3" + }, + "urllib3": { + "hashes": [ + "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", + "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.25.11" + }, + "virtualenv": { + "hashes": [ + "sha256:b0011228208944ce71052987437d3843e05690b2f23d1c7da4263fde104c97a2", + "sha256:b8d6110f493af256a40d65e29846c69340a947669eec8ce784fcf3dd3af28380" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.1.0" + }, + "websocket-client": { + "hashes": [ + "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549", + "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010" + ], + "version": "==0.57.0" + }, + "wrapt": { + "hashes": [ + "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" + ], + "version": "==1.12.1" + }, + "zipp": { + "hashes": [ + "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108", + "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.0" + } + } +} diff --git a/bootstrap b/bootstrap deleted file mode 100755 index ed46be4..0000000 --- a/bootstrap +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash -# -# NAME -# bootstrap -- initialize/update docker environment -# -# SYNOPSIS -# bootstrap -# bootstrap shellinit -# -# DESCRIPTION -# Execute this script without parameters to build the local docker -# environment. Once bootstrapped, dependent services are running -# via docker-compose and the environment variables are written to -# *build/test-environment* for future use. -# -# Running this script with the _shellinit_ command line parameter -# causes it to simply interrogate the running docker environment, -# update *build/test-environment*, and print the environment to -# the standard output stream in a shell executable manner. This -# makes the following pattern for setting environment variables -# in the current shell work. -# -# prompt% $(./bootstrap shellinit) -# -# vim: set ts=2 sts=2 sw=2 et: -set -e - -get_container() { - echo $(echo "${DOCKER_COMPOSE_PREFIX}" | tr -d -- '-.')_$1_1 -} - -get_ipaddr() { - docker inspect --format '{{ .NetworkSettings.IPAddress }}' $1 -} - -get_exposed_port() { - docker-compose ${COMPOSE_ARGS} port $1 $2 | cut -d: -f2 -} - -report_start() { - printf "Waiting for $1 ... " -} - -report_done() { - printf "${COLOR_GREEN}done${COLOR_RESET}\n" -} - -# Ensure Docker is Running -if test -e /var/run/docker.sock -then - DOCKER_IP=127.0.0.1 -else - echo "Docker environment not detected." - exit 1 -fi - -# Activate the virtual environment -if test -e env/bin/activate -then - . ./env/bin/activate -fi - -mkdir -p build - -# Common constants -COLOR_RESET='\033[0m' -COLOR_GREEN='\033[0;32m' -PREFIX=${PWD##*/} -DOCKER_COMPOSE_PREFIX=${PREFIX:-${DOCKER_COMPOSE_PREFIX}} -COMPOSE_ARGS="-p ${DOCKER_COMPOSE_PREFIX}" - -# Stop any running instances and clean up after them, then pull images -docker-compose ${COMPOSE_ARGS} down --volumes --remove-orphans -docker-compose ${COMPOSE_ARGS} pull -docker-compose ${COMPOSE_ARGS} up -d - -cat > build/test-environment< - - - - - - - - - diff --git a/consulate/__init__.py b/consulate/__init__.py index 2a56375..692bc24 100644 --- a/consulate/__init__.py +++ b/consulate/__init__.py @@ -2,35 +2,40 @@ Consulate: A client library for Consul """ +import logging +from logging import NullHandler + from consulate.client import Consul +from consulate.exceptions import ( + ACLDisabled, + ClientError, + ConsulateException, + Forbidden, + LockFailure, + NotFound, + RequestError, + ServerError, +) -from consulate.exceptions import (ConsulateException, - ClientError, - ServerError, - ACLDisabled, - Forbidden, - NotFound, - LockFailure, - RequestError) +__version__ = "1.0.0" -import logging -from logging import NullHandler +__author__ = "Gavin M. Roy" +__email__ = "gavinr@aweber.com" -__version__ = '1.0.0' +__maintainer__ = "Alberto Vara" +__maintainer_email__ = "a.vara.1986@gmail.com" # Prevent undesired log output to the root logger -logging.getLogger('consulate').addHandler(NullHandler()) - +logging.getLogger("consulate").addHandler(NullHandler()) __all__ = [ - __version__, - Consul, - ConsulateException, - ClientError, - ServerError, - ACLDisabled, - Forbidden, - NotFound, - LockFailure, - RequestError + "Consul", + "ConsulateException", + "ClientError", + "ServerError", + "ACLDisabled", + "Forbidden", + "NotFound", + "LockFailure", + "RequestError", ] diff --git a/consulate/adapters.py b/consulate/adapters.py index 44e8bf5..8955908 100644 --- a/consulate/adapters.py +++ b/consulate/adapters.py @@ -9,6 +9,7 @@ import requests import requests.exceptions + try: import requests_unixsocket except ImportError: # pragma: no cover @@ -18,8 +19,8 @@ LOGGER = logging.getLogger(__name__) -CONTENT_FORM = 'application/x-www-form-urlencoded; charset=utf-8' -CONTENT_JSON = 'application/json; charset=utf-8' +CONTENT_FORM = "application/x-www-form-urlencoded; charset=utf-8" +CONTENT_JSON = "application/json; charset=utf-8" def prepare_data(fun): @@ -36,18 +37,17 @@ def inner(*args, **kwargs): :param dict kwargs: keyword arguments """ - if kwargs.get('data'): - if not utils.is_string(kwargs.get('data')): - kwargs['data'] = json.dumps(kwargs['data']) - elif len(args) == 3 and \ - not (utils.is_string(args[2]) or args[2] is None): + if kwargs.get("data"): + if not utils.is_string(kwargs.get("data")): + kwargs["data"] = json.dumps(kwargs["data"]) + elif len(args) == 3 and not (utils.is_string(args[2]) or args[2] is None): args = args[0], args[1], json.dumps(args[2]) return fun(*args, **kwargs) return inner -class Request(object): +class Request: """The Request adapter class""" def __init__(self, timeout=None, verify=True, cert=None): @@ -83,10 +83,8 @@ def get(self, uri, timeout=None): """ LOGGER.debug("GET %s", uri) try: - return api.Response(self.session.get( - uri, timeout=timeout or self.timeout)) - except (requests.exceptions.RequestException, - OSError, socket.error) as err: + return api.Response(self.session.get(uri, timeout=timeout or self.timeout)) + except (requests.exceptions.RequestException, OSError, socket.error) as err: raise exceptions.RequestError(str(err)) def get_stream(self, uri): @@ -99,12 +97,11 @@ def get_stream(self, uri): LOGGER.debug("GET Stream from %s", uri) try: response = self.session.get(uri, stream=True) - except (requests.exceptions.RequestException, - OSError, socket.error) as err: + except (requests.exceptions.RequestException, OSError, socket.error) as err: raise exceptions.RequestError(str(err)) if utils.response_ok(response): for line in response.iter_lines(): # pragma: no cover - yield line.decode('utf-8') + yield line.decode("utf-8") @prepare_data def put(self, uri, data=None, timeout=None): @@ -118,17 +115,10 @@ def put(self, uri, data=None, timeout=None): """ LOGGER.debug("PUT %s with %r", uri, data) - headers = { - 'Content-Type': CONTENT_FORM - if utils.is_string(data) else CONTENT_JSON - } + headers = {"Content-Type": CONTENT_FORM if utils.is_string(data) else CONTENT_JSON} try: - return api.Response( - self.session.put( - uri, data=data, headers=headers, - timeout=timeout or self.timeout)) - except (requests.exceptions.RequestException, - OSError, socket.error) as err: + return api.Response(self.session.put(uri, data=data, headers=headers, timeout=timeout or self.timeout)) + except (requests.exceptions.RequestException, OSError, socket.error) as err: raise exceptions.RequestError(str(err)) @@ -136,5 +126,5 @@ class UnixSocketRequest(Request): # pragma: no cover """Use to communicate with Consul over a Unix socket""" def __init__(self, timeout=None): - super(UnixSocketRequest, self).__init__(timeout) + super().__init__(timeout) self.session = requests_unixsocket.Session() diff --git a/consulate/api/__init__.py b/consulate/api/__init__.py index 67bda00..f707b33 100644 --- a/consulate/api/__init__.py +++ b/consulate/api/__init__.py @@ -4,25 +4,14 @@ """ from consulate.api.acl import ACL from consulate.api.agent import Agent +from consulate.api.base import Response from consulate.api.catalog import Catalog +from consulate.api.coordinate import Coordinate from consulate.api.event import Event from consulate.api.health import Health -from consulate.api.coordinate import Coordinate from consulate.api.kv import KV from consulate.api.lock import Lock from consulate.api.session import Session from consulate.api.status import Status -from consulate.api.base import Response -__all__ = [ - ACL, - Agent, - Catalog, - Event, - Health, - KV, - Lock, - Session, - Status, - Response -] +__all__ = ["ACL", "Agent", "Catalog", "Event", "Health", "KV", "Lock", "Session", "Status", "Response", "Coordinate"] diff --git a/consulate/api/acl.py b/consulate/api/acl.py index c10317c..e910c0e 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -4,13 +4,15 @@ """ import logging -from consulate.models import acl as model -from consulate.api import base from consulate import exceptions +from consulate.api import base +from consulate.models import acl as model + # from typing import List, Dict, Union LOGGER = logging.getLogger(__name__) + # ServiceIdentity = Dict[str, Union[str, List[str]]] # ServiceIdentities = List[ServiceIdentity] # PolicyLink = Dict[str, str] @@ -19,11 +21,12 @@ # RoleLinks = List[RoleLink] -class ACL(base.Endpoint): +class ACL(base.Endpoint): # pylint: disable=too-many-public-methods """The ACL endpoints are used to create, update, destroy, and query ACL tokens. """ + def list_policies(self): """List all ACL policies available in cluster. @@ -32,7 +35,7 @@ def list_policies(self): """ return self._get(["policies"]) - def read_policy(self, id): + def read_policy(self, id): # pylint: disable=redefined-builtin """Read an existing policy with the given ID. :param str id: The ID of the policy. @@ -41,11 +44,7 @@ def read_policy(self, id): """ return self._get(["policy", id]) - def create_policy(self, - name, - datacenters=None, - description=None, - rules=None): + def create_policy(self, name, datacenters=None, description=None, rules=None): """Create policy with name given and rules. :param str name: name of the policy @@ -55,20 +54,15 @@ def create_policy(self, :param rtype: dict """ - return self._put_response_body(["policy"], {}, - dict( - model.ACLPolicy( - name=name, - datacenters=datacenters, - description=description, - rules=rules))) - - def update_policy(self, - id, - name, - datacenters=None, - description=None, - rules=None): + return self._put_response_body( + ["policy"], + {}, + dict(model.ACLPolicy(name=name, datacenters=datacenters, description=description, rules=rules)), + ) + + def update_policy( + self, id, name, datacenters=None, description=None, rules=None + ): # pylint: disable=redefined-builtin """Update policy with id given. :param str id: A UUID for the policy to update. @@ -79,15 +73,13 @@ def update_policy(self, :param rtype: dict """ - return self._put_response_body(["policy", id], {}, - dict( - model.ACLPolicy( - name=name, - datacenters=datacenters, - description=description, - rules=rules))) - - def delete_policy(self, id): + return self._put_response_body( + ["policy", id], + {}, + dict(model.ACLPolicy(name=name, datacenters=datacenters, description=description, rules=rules)), + ) + + def delete_policy(self, id): # pylint: disable=redefined-builtin """Delete an existing policy with the given ID. :param str id: The ID of the policy. @@ -103,7 +95,7 @@ def list_roles(self): """ return self._get(["roles"]) - def read_role(self, id=None, name=None): + def read_role(self, id=None, name=None): # pylint: disable=redefined-builtin """Read an existing role with the given ID or Name. :param str id: The ID of the role. @@ -111,18 +103,14 @@ def read_role(self, id=None, name=None): :param rtype: dict """ - if id is not None: + if id is not None: # pylint: disable=no-else-return return self._get(["role", id]) elif name is not None: return self._get(["role", "name", name]) else: raise exceptions.NotFound("Either id or name must be specified") - def create_role(self, - name, - description=None, - policies=None, - service_identities=None): + def create_role(self, name, description=None, policies=None, service_identities=None): """Create an ACL role from a list of policies or service identities. :param str name: The name of the ACL role. Must be unique. @@ -133,19 +121,18 @@ def create_role(self, """ return self._put_response_body( - ["role"], {}, + ["role"], + {}, dict( - model.ACLRole(name=name, - description=description, - policies=policies, - service_identities=service_identities))) - - def update_role(self, - id, - name, - description=None, - policies=None, - service_identities=None): + model.ACLRole( + name=name, description=description, policies=policies, service_identities=service_identities + ) + ), + ) + + def update_role( + self, id, name, description=None, policies=None, service_identities=None + ): # pylint: disable=redefined-builtin """Update role with id given. :param str id: A UUID for the policy to update. @@ -157,14 +144,16 @@ def update_role(self, """ return self._put_response_body( - ["role", id], {}, + ["role", id], + {}, dict( - model.ACLRole(name=name, - description=description, - policies=policies, - service_identities=service_identities))) + model.ACLRole( + name=name, description=description, policies=policies, service_identities=service_identities + ) + ), + ) - def delete_role(self, id): + def delete_role(self, id): # pylint: disable=redefined-builtin """Delete an existing role with the given ID. :param str id: The ID of the role. @@ -198,16 +187,18 @@ def read_self_token(self): """ return self._get(["token", "self"]) - def create_token(self, - accessor_id=None, - description=None, - expiration_time=None, - expiration_ttl=None, - local=False, - policies=None, - roles=None, - secret_id=None, - service_identities=None): + def create_token( + self, + accessor_id=None, + description=None, + expiration_time=None, + expiration_ttl=None, + local=False, + policies=None, + roles=None, + secret_id=None, + service_identities=None, + ): # pylint: disable=too-many-arguments """Create a token from the roles, policies, and service identities provided. @@ -226,28 +217,35 @@ def create_token(self, """ return self._put_response_body( - ["token"], {}, + ["token"], + {}, dict( - model.ACLToken(accessor_id=accessor_id, - description=description, - expiration_time=expiration_time, - expiration_ttl=expiration_ttl, - local=local, - policies=policies, - roles=roles, - secret_id=secret_id, - service_identities=service_identities))) - - def update_token(self, - accessor_id, - description=None, - expiration_time=None, - expiration_ttl=None, - local=False, - policies=None, - roles=None, - secret_id=None, - service_identities=None): + model.ACLToken( + accessor_id=accessor_id, + description=description, + expiration_time=expiration_time, + expiration_ttl=expiration_ttl, + local=local, + policies=policies, + roles=roles, + secret_id=secret_id, + service_identities=service_identities, + ) + ), + ) + + def update_token( + self, + accessor_id, + description=None, + expiration_time=None, + expiration_ttl=None, + local=False, + policies=None, + roles=None, + secret_id=None, + service_identities=None, + ): # pylint: disable=too-many-arguments """Create a token from the roles, policies, and service identities provided. @@ -266,17 +264,22 @@ def update_token(self, """ return self._put_response_body( - ["token", accessor_id], {}, + ["token", accessor_id], + {}, dict( - model.ACLToken(accessor_id=accessor_id, - description=description, - expiration_time=expiration_time, - expiration_ttl=expiration_ttl, - local=local, - policies=policies, - roles=roles, - secret_id=secret_id, - service_identities=service_identities))) + model.ACLToken( + accessor_id=accessor_id, + description=description, + expiration_time=expiration_time, + expiration_ttl=expiration_ttl, + local=local, + policies=policies, + roles=roles, + secret_id=secret_id, + service_identities=service_identities, + ) + ), + ) def clone_token(self, accessor_id, description=None): """Clone a token by the accessor_id. @@ -287,8 +290,8 @@ def clone_token(self, accessor_id, description=None): """ return self._put_response_body( - ["token", accessor_id, "clone"], {}, - dict(model.ACLToken(description=description))) + ["token", accessor_id, "clone"], {}, dict(model.ACLToken(description=description)) + ) def delete_token(self, accessor_id): """Delete an existing token with the given AcccessorID. @@ -323,9 +326,9 @@ def bootstrap(self): :raises: :exc:`~consulate.exceptions.Forbidden` """ - return self._put_response_body(['bootstrap'])['ID'] + return self._put_response_body(["bootstrap"])["ID"] - def create(self, name, acl_type='client', rules=None): + def create(self, name, acl_type="client", rules=None): """The create endpoint is used to make a new token. A token has a name, a type, and a set of ACL rules. @@ -354,11 +357,7 @@ def create(self, name, acl_type='client', rules=None): :raises: consulate.exceptions.Forbidden """ - return self._put_response_body(['create'], {}, - dict( - model.ACL(name=name, - type=acl_type, - rules=rules)))['ID'] + return self._put_response_body(["create"], {}, dict(model.ACL(name=name, type=acl_type, rules=rules)))["ID"] def clone(self, acl_id): """Clone an existing ACL returning the new ACL ID @@ -368,7 +367,7 @@ def clone(self, acl_id): :raises: consulate.exceptions.Forbidden """ - return self._put_response_body(['clone', acl_id])['ID'] + return self._put_response_body(["clone", acl_id])["ID"] def destroy(self, acl_id): """Delete the specified ACL @@ -378,7 +377,7 @@ def destroy(self, acl_id): :raises: consulate.exceptions.Forbidden """ - response = self._adapter.put(self._build_uri(['destroy', acl_id])) + response = self._adapter.put(self._build_uri(["destroy", acl_id])) if response.status_code == 403: raise exceptions.Forbidden(response.body) return response.status_code == 200 @@ -392,9 +391,9 @@ def info(self, acl_id): :raises: consulate.exceptions.NotFound """ - response = self._get(['info', acl_id], raise_on_404=True) + response = self._get(["info", acl_id], raise_on_404=True) if not response: - raise exceptions.NotFound('ACL not found') + raise exceptions.NotFound("ACL not found") return response def list(self): @@ -404,7 +403,7 @@ def list(self): :raises: consulate.exceptions.Forbidden """ - return self._get(['list']) + return self._get(["list"]) def replication(self): """Return the status of the ACL replication process in the datacenter. @@ -418,9 +417,9 @@ def replication(self): :raises: consulate.exceptions.Forbidden """ - return self._get(['replication']) + return self._get(["replication"]) - def update(self, acl_id, name, acl_type='client', rules=None): + def update(self, acl_id, name, acl_type="client", rules=None): """Update an existing ACL, updating its values or add a new ACL if the ACL ID specified is not found. @@ -434,9 +433,6 @@ def update(self, acl_id, name, acl_type='client', rules=None): :raises: consulate.exceptions.Forbidden """ - return self._put_response_body(['update'], {}, - dict( - model.ACL(id=acl_id, - name=name, - type=acl_type, - rules=rules)))['ID'] + return self._put_response_body( + ["update"], {}, dict(model.ACL(id=acl_id, name=name, type=acl_type, rules=rules)) + )["ID"] diff --git a/consulate/api/agent.py b/consulate/api/agent.py index 75e52c9..76c2d70 100644 --- a/consulate/api/agent.py +++ b/consulate/api/agent.py @@ -5,12 +5,7 @@ from consulate.api import base from consulate.models import agent as models -_TOKENS = [ - 'acl_token', - 'acl_agent_token', - 'acl_agent_master_token', - 'acl_replication_token' -] +_TOKENS = ["acl_token", "acl_agent_token", "acl_agent_master_token", "acl_replication_token"] class Agent(base.Endpoint): @@ -30,10 +25,9 @@ def __init__(self, uri, adapter, datacenter=None, token=None): :param str token: Access Token """ - super(Agent, self).__init__(uri, adapter, datacenter, token) + super().__init__(uri, adapter, datacenter, token) self.check = Agent.Check(self._base_uri, adapter, datacenter, token) - self.service = Agent.Service( - self._base_uri, adapter, datacenter, token) + self.service = Agent.Service(self._base_uri, adapter, datacenter, token) class Check(base.Endpoint): """One of the primary roles of the agent is the management of system @@ -64,25 +58,27 @@ class Check(base.Endpoint): """ - def register(self, - name, - check_id=None, - interval=None, - notes=None, - deregister_critical_service_after=None, - args=None, - docker_container_id=None, - grpc=None, - grpc_use_tls=None, - http=None, - http_method=None, - header=None, - timeout=None, - tls_skip_verify=None, - tcp=None, - ttl=None, - service_id=None, - status=None): + def register( + self, + name, + check_id=None, + interval=None, + notes=None, + deregister_critical_service_after=None, + args=None, + docker_container_id=None, + grpc=None, + grpc_use_tls=None, + http=None, + http_method=None, + header=None, + timeout=None, + tls_skip_verify=None, + tcp=None, + ttl=None, + service_id=None, + status=None, + ): # pylint: disable=too-many-arguments,too-many-locals """Add a new check to the local agent. Checks are either a script or TTL type. The agent is responsible for managing the status of the check and keeping the Catalog in sync. @@ -111,14 +107,15 @@ def register(self, """ return self._put_no_response_body( - ['register'], None, dict( + ["register"], + None, + dict( models.Check( name=name, id=check_id, interval=interval, notes=notes, - deregister_critical_service_after= - deregister_critical_service_after, + deregister_critical_service_after=deregister_critical_service_after, args=args, docker_container_id=docker_container_id, grpc=grpc, @@ -131,7 +128,10 @@ def register(self, tcp=tcp, ttl=ttl, service_id=service_id, - status=status))) + status=status, + ) + ), + ) def deregister(self, check_id): """Remove a check from the local agent. The agent will take care @@ -141,7 +141,7 @@ def deregister(self, check_id): :rtype: bool """ - return self._put_no_response_body(['deregister', check_id]) + return self._put_no_response_body(["deregister", check_id]) def ttl_pass(self, check_id, note=None): """This endpoint is used with a check that is of the TTL type. @@ -153,8 +153,7 @@ def ttl_pass(self, check_id, note=None): :rtype: bool """ - return self._put_no_response_body( - ['pass', check_id], {'note': note} if note else None) + return self._put_no_response_body(["pass", check_id], {"note": note} if note else None) def ttl_warn(self, check_id, note=None): """This endpoint is used with a check that is of the TTL type. @@ -166,8 +165,7 @@ def ttl_warn(self, check_id, note=None): :rtype: bool """ - return self._put_no_response_body( - ['warn', check_id], {'note': note} if note else None) + return self._put_no_response_body(["warn", check_id], {"note": note} if note else None) def ttl_fail(self, check_id, note=None): """This endpoint is used with a check that is of the TTL type. @@ -179,8 +177,7 @@ def ttl_fail(self, check_id, note=None): :rtype: bool """ - return self._put_no_response_body( - ['fail', check_id], {'note': note} if note else None) + return self._put_no_response_body(["fail", check_id], {"note": note} if note else None) class Service(base.Endpoint): """One of the main goals of service discovery is to provide a catalog @@ -192,16 +189,19 @@ class Service(base.Endpoint): the HTTP interface. """ - def register(self, - name, - service_id=None, - address=None, - port=None, - tags=None, - meta=None, - check=None, - checks=None, - enable_tag_override=None): + + def register( + self, + name, + service_id=None, + address=None, + port=None, + tags=None, + meta=None, + check=None, + checks=None, + enable_tag_override=None, + ): # pylint: disable=too-many-arguments """Add a new service to the local agent. :param str name: The name of the service @@ -220,11 +220,22 @@ def register(self, """ return self._put_no_response_body( - ['register'], None, - dict(models.Service( - name=name, id=service_id, address=address, port=port, - tags=tags, meta=meta, check=check, checks=checks, - enable_tag_override=enable_tag_override))) + ["register"], + None, + dict( + models.Service( + name=name, + id=service_id, + address=address, + port=port, + tags=tags, + meta=meta, + check=check, + checks=checks, + enable_tag_override=enable_tag_override, + ) + ), + ) def deregister(self, service_id): """Deregister the service from the local agent. The agent will @@ -235,7 +246,7 @@ def deregister(self, service_id): :rtype: bool """ - return self._put_no_response_body(['deregister', service_id]) + return self._put_no_response_body(["deregister", service_id]) def maintenance(self, service_id, enable=True, reason=None): """Place given service into "maintenance mode". @@ -246,11 +257,10 @@ def maintenance(self, service_id, enable=True, reason=None): :rtype: bool """ - query_params = {'enable': enable} + query_params = {"enable": enable} if reason: - query_params['reason'] = reason - return self._put_no_response_body(['maintenance', service_id], - query_params) + query_params["reason"] = reason + return self._put_no_response_body(["maintenance", service_id], query_params) def checks(self): """Return the all the checks that are registered with the local agent. @@ -264,7 +274,7 @@ def checks(self): :rtype: dict """ - return self._get(['checks']) + return self._get(["checks"]) def force_leave(self, node): """Instructs the agent to force a node into the left state. If a node @@ -274,7 +284,7 @@ def force_leave(self, node): node into the left state allows its old entries to be removed. """ - return self._put_no_response_body(['force-leave', node]) + return self._put_no_response_body(["force-leave", node]) def join(self, address, wan=False): """This endpoint is hit with a GET and is used to instruct the agent @@ -287,8 +297,8 @@ def join(self, address, wan=False): :rtype: bool """ - query_params = {'wan': 1} if wan else None - return self._put_no_response_body(['join', address], query_params) + query_params = {"wan": 1} if wan else None + return self._put_no_response_body(["join", address], query_params) def maintenance(self, enable=True, reason=None): """Places the agent into or removes the agent from "maintenance mode". @@ -300,10 +310,10 @@ def maintenance(self, enable=True, reason=None): :rtype: bool """ - query_params = {'enable': enable} + query_params = {"enable": enable} if reason: - query_params['reason'] = reason - return self._put_no_response_body(['maintenance'], query_params) + query_params["reason"] = reason + return self._put_no_response_body(["maintenance"], query_params) def members(self): """Returns the members the agent sees in the cluster gossip pool. @@ -314,7 +324,7 @@ def members(self): :rtype: list """ - return self._get_list(['members']) + return self._get_list(["members"]) def metrics(self): """Returns agent's metrics for the most recent finished interval @@ -324,7 +334,7 @@ def metrics(self): :rtype: dict """ - return self._get(['metrics']) + return self._get(["metrics"]) def monitor(self): """Iterator over logs from the local agent. @@ -334,7 +344,7 @@ def monitor(self): :rtype: iterator """ - for line in self._get_stream(['monitor']): + for line in self._get_stream(["monitor"]): yield line def reload(self): @@ -346,7 +356,7 @@ def reload(self): :rtype: list """ - return self._put_response_body(['reload']) or None + return self._put_response_body(["reload"]) or None def services(self): """return the all the services that are registered with the local @@ -361,16 +371,16 @@ def services(self): :rtype: dict """ - return self._get(['services']) + return self._get(["services"]) def self(self): - """ This endpoint is used to return the configuration and member + """This endpoint is used to return the configuration and member information of the local agent under the Config key. :rtype: dict """ - return self._get(['self']) + return self._get(["self"]) def token(self, name, value): """Update the ACL tokens currently in use by the agent. It can be used @@ -395,6 +405,5 @@ def token(self, name, value): """ if name not in _TOKENS: - raise ValueError('Invalid token name: {}'.format(name)) - return self._put_no_response_body( - ['token', name], {}, {'Token': value}) + raise ValueError("Invalid token name: {}".format(name)) + return self._put_no_response_body(["token", name], {}, {"Token": value}) diff --git a/consulate/api/base.py b/consulate/api/base.py index cdc8594..3424e3e 100644 --- a/consulate/api/base.py +++ b/consulate/api/base.py @@ -4,18 +4,15 @@ """ import base64 import json -try: - from urllib.parse import urlencode # Python 3 -except ImportError: - from urllib import urlencode # Python 2 +from urllib.parse import urlencode # Python 3 from consulate import utils -class Endpoint(object): +class Endpoint: """Base class for API endpoints""" - KEYWORD = '' + KEYWORD = "" def __init__(self, uri, adapter, datacenter=None, token=None): """Create a new instance of the Endpoint class @@ -27,7 +24,7 @@ def __init__(self, uri, adapter, datacenter=None, token=None): """ self._adapter = adapter - self._base_uri = '{0}/{1}'.format(uri, self.__class__.__name__.lower()) + self._base_uri = "{0}/{1}".format(uri, self.__class__.__name__.lower()) self._dc = datacenter self._token = token @@ -41,17 +38,15 @@ def _build_uri(self, params, query_params=None): if not query_params: query_params = dict() if self._dc: - query_params['dc'] = self._dc + query_params["dc"] = self._dc if self._token: - query_params['token'] = self._token - path = '/'.join(params) + query_params["token"] = self._token + path = "/".join(params) if query_params: - return '{0}/{1}?{2}'.format(self._base_uri, path, - urlencode(query_params)) - return '{0}/{1}'.format(self._base_uri, path) + return "{0}/{1}?{2}".format(self._base_uri, path, urlencode(query_params)) + return "{0}/{1}".format(self._base_uri, path) - def _get(self, params, query_params=None, raise_on_404=False, - timeout=None): + def _get(self, params, query_params=None, raise_on_404=False, timeout=None): """Perform a GET request :param list params: List of path parts @@ -61,16 +56,15 @@ def _get(self, params, query_params=None, raise_on_404=False, :rtype: dict or list or None """ - response = self._adapter.get(self._build_uri(params, query_params), - timeout=timeout) + response = self._adapter.get(self._build_uri(params, query_params), timeout=timeout) if utils.response_ok(response, raise_on_404): return response.body return [] def _delete( - self, - params, - raise_on_404=False, + self, + params, + raise_on_404=False, ): """Perform a DELETE request @@ -103,31 +97,29 @@ def _get_stream(self, params, query_params=None): :rtype: iterator """ - for line in self._adapter.get_stream( - self._build_uri(params, query_params)): + for line in self._adapter.get_stream(self._build_uri(params, query_params)): yield line def _get_no_response_body(self, url_parts, query=None): - return utils.response_ok( - self._adapter.get(self._build_uri(url_parts, query))) + return utils.response_ok(self._adapter.get(self._build_uri(url_parts, query))) def _get_response_body(self, url_parts, query=None): response = self._adapter.get(self._build_uri(url_parts, query)) if utils.response_ok(response): return response.body + return None def _put_no_response_body(self, url_parts, query=None, payload=None): - return utils.response_ok( - self._adapter.put(self._build_uri(url_parts, query), payload)) + return utils.response_ok(self._adapter.put(self._build_uri(url_parts, query), payload)) def _put_response_body(self, url_parts, query=None, payload=None): - response = self._adapter.put(self._build_uri(url_parts, query), - data=payload) + response = self._adapter.put(self._build_uri(url_parts, query), data=payload) if utils.response_ok(response): return response.body + return None -class Response(object): +class Response: """Used to process and wrap the responses from Consul. :param int status_code: HTTP Status code @@ -135,6 +127,7 @@ class Response(object): :param dict headers: Response headers """ + status_code = None body = None headers = None @@ -149,7 +142,7 @@ def __init__(self, response): self.body = self._demarshal(response.content) self.headers = response.headers - def _demarshal(self, body): + def _demarshal(self, body): # pylint: disable=too-many-branches,too-many-return-statements; # noqa: C901 """Demarshal the request payload. :param str body: The string response body @@ -158,11 +151,11 @@ def _demarshal(self, body): """ if body is None: return None - if self.status_code == 200: + if self.status_code == 200: # pylint: disable=too-many-nested-blocks try: - if utils.PYTHON3 and isinstance(body, bytes): + if isinstance(body, bytes): try: - body = body.decode('utf-8') + body = body.decode("utf-8") except UnicodeDecodeError: pass value = json.loads(body) @@ -172,14 +165,14 @@ def _demarshal(self, body): return None if isinstance(value, bool): return value - if 'error' not in value: + if "error" not in value: for row in value: - if 'Value' in row: + if "Value" in row: try: - row['Value'] = base64.b64decode(row['Value']) - if isinstance(row['Value'], bytes): + row["Value"] = base64.b64decode(row["Value"]) + if isinstance(row["Value"], bytes): try: - row['Value'] = row['Value'].decode('utf-8') + row["Value"] = row["Value"].decode("utf-8") except UnicodeDecodeError: pass except TypeError: diff --git a/consulate/api/catalog.py b/consulate/api/catalog.py index 502dc43..76b2a93 100644 --- a/consulate/api/catalog.py +++ b/consulate/api/catalog.py @@ -14,13 +14,9 @@ class Catalog(base.Endpoint): """ def __init__(self, uri, adapter, dc=None, token=None): - super(Catalog, self).__init__(uri, adapter, dc, token) + super().__init__(uri, adapter, dc, token) - def register(self, node, address, - datacenter=None, - service=None, - check=None, - node_meta=None): + def register(self, node, address, datacenter=None, service=None, check=None, node_meta=None): """A a low level mechanism for directly registering or updating entries in the catalog. It is usually recommended to use the agent local endpoints, as they are simpler and perform anti-entropy. @@ -94,20 +90,19 @@ def register(self, node, address, :rtype: bool """ - payload = {'Node': node, 'Address': address} + payload = {"Node": node, "Address": address} if datacenter: - payload['Datacenter'] = datacenter + payload["Datacenter"] = datacenter if service: - payload['Service'] = service + payload["Service"] = service if check: - payload['Check'] = check + payload["Check"] = check if node_meta: - payload['NodeMeta'] = node_meta + payload["NodeMeta"] = node_meta - return self._put_response_body(['register'], None, payload) + return self._put_response_body(["register"], None, payload) - def deregister(self, node, datacenter=None, - check_id=None, service_id=None): + def deregister(self, node, datacenter=None, check_id=None, service_id=None): """Directly remove entries in the catalog. It is usually recommended to use the agent local endpoints, as they are simpler and perform anti-entropy. @@ -127,14 +122,14 @@ def deregister(self, node, datacenter=None, :rtype: bool """ - payload = {'Node': node} + payload = {"Node": node} if datacenter: - payload['Datacenter'] = datacenter + payload["Datacenter"] = datacenter if check_id: - payload['CheckID'] = check_id + payload["CheckID"] = check_id if service_id: - payload['ServiceID'] = service_id - return self._put_response_body(['deregister'], None, payload) + payload["ServiceID"] = service_id + return self._put_response_body(["deregister"], None, payload) def datacenters(self): """Return all the datacenters that are known by the Consul server. @@ -142,7 +137,7 @@ def datacenters(self): :rtype: list """ - return self._get_list(['datacenters']) + return self._get_list(["datacenters"]) def node(self, node_id): """Return the node data for the specified node @@ -151,7 +146,7 @@ def node(self, node_id): :rtype: dict """ - return self._get(['node', node_id]) + return self._get(["node", node_id]) def nodes(self, node_meta=None): """Return all of the nodes for the current datacenter. @@ -160,8 +155,8 @@ def nodes(self, node_meta=None): :rtype: list """ - query_params = {'node-meta': node_meta} if node_meta else {} - return self._get_list(['nodes'], query_params) + query_params = {"node-meta": node_meta} if node_meta else {} + return self._get_list(["nodes"], query_params) def service(self, service_id): """Return the service details for the given service @@ -170,7 +165,7 @@ def service(self, service_id): :rtype: list """ - return self._get_list(['service', service_id]) + return self._get_list(["service", service_id]) def services(self): """Return a list of all of the services for the current datacenter. @@ -178,4 +173,4 @@ def services(self): :rtype: list """ - return self._get_list(['services']) + return self._get_list(["services"]) diff --git a/consulate/api/coordinate.py b/consulate/api/coordinate.py index be8b396..e1d57c5 100644 --- a/consulate/api/coordinate.py +++ b/consulate/api/coordinate.py @@ -2,12 +2,13 @@ Consul Coordinate Endpoint Access """ -from consulate.api import base from math import sqrt +from consulate.api import base + + class Coordinate(base.Endpoint): - """Used to query node coordinates. - """ + """Used to query node coordinates.""" def node(self, node_id): """Return coordinates for the given node. @@ -16,7 +17,7 @@ def node(self, node_id): :rtype: dict """ - return self._get(['node', node_id]) + return self._get(["node", node_id]) def nodes(self): """Return coordinates for the current datacenter. @@ -24,39 +25,38 @@ def nodes(self): :rtype: list """ - return self._get_list(['nodes']) + return self._get_list(["nodes"]) - def rtt(self, src, dst): + @staticmethod + def rtt(src, dst): """Calculated RTT between two node coordinates. - - :param dict src - :param dict dst - :rtype float - + :param src: + :param dst: + :return: """ if not isinstance(src, (dict)): - raise ValueError('coordinate object must be a dictionary') + raise ValueError("coordinate object must be a dictionary") if not isinstance(dst, (dict)): - raise ValueError('coordinate object must be a dictionary') - if 'Coord' not in src: - raise ValueError('coordinate object has no Coord key') - if 'Coord' not in dst: - raise ValueError('coordinate object has no Coord key') + raise ValueError("coordinate object must be a dictionary") + if "Coord" not in src: + raise ValueError("coordinate object has no Coord key") + if "Coord" not in dst: + raise ValueError("coordinate object has no Coord key") - src_coord = src['Coord'] - dst_coord = dst['Coord'] + src_coord = src["Coord"] + dst_coord = dst["Coord"] - if len(src_coord.get('Vec')) != len(dst_coord.get('Vec')): - raise ValueError('coordinate objects are not compatible due to different length') + if len(src_coord.get("Vec")) != len(dst_coord.get("Vec")): + raise ValueError("coordinate objects are not compatible due to different length") sumsq = 0.0 - for i in xrange(len(src_coord.get('Vec'))): - diff = src_coord.get('Vec')[i] - dst_coord.get('Vec')[i] + for i in range(len(src_coord.get("Vec"))): + diff = src_coord.get("Vec")[i] - dst_coord.get("Vec")[i] sumsq += diff * diff - rtt = sqrt(sumsq) + src_coord.get('Height') + dst_coord.get('Height') - adjusted = rtt + src_coord.get('Adjustment') + dst_coord.get('Adjustment') + rtt = sqrt(sumsq) + src_coord.get("Height") + dst_coord.get("Height") + adjusted = rtt + src_coord.get("Adjustment") + dst_coord.get("Adjustment") if adjusted > 0.0: rtt = adjusted diff --git a/consulate/api/event.py b/consulate/api/event.py index 0b4248a..e509ee2 100644 --- a/consulate/api/event.py +++ b/consulate/api/event.py @@ -6,16 +6,9 @@ class Event(base.Endpoint): - """The Event endpoints are used to fire a new event and list recent events. + """The Event endpoints are used to fire a new event and list recent events.""" - """ - - def fire(self, name, - payload=None, - datacenter=None, - node=None, - service=None, - tag=None): + def fire(self, name, payload=None, datacenter=None, node=None, service=None, tag=None): """Trigger a new user Event :param str name: The name of the event @@ -29,16 +22,15 @@ def fire(self, name, """ query_args = {} if datacenter: - query_args['dc'] = datacenter + query_args["dc"] = datacenter if node: - query_args['node'] = node + query_args["node"] = node if service: - query_args['service'] = service + query_args["service"] = service if tag: - query_args['tag'] = tag - response = self._adapter.put(self._build_uri(['fire', name], - query_args), payload) - return response.body.get('ID') + query_args["tag"] = tag + response = self._adapter.put(self._build_uri(["fire", name], query_args), payload) + return response.body.get("ID") def list(self, name=None): """Returns the most recent events known by the agent. As a consequence @@ -51,5 +43,5 @@ def list(self, name=None): """ query_args = {} if name: - query_args['name'] = name - return self._get(['list'], query_args) + query_args["name"] = name + return self._get(["list"], query_args) diff --git a/consulate/api/health.py b/consulate/api/health.py index 0a92b4c..7000419 100644 --- a/consulate/api/health.py +++ b/consulate/api/health.py @@ -22,8 +22,8 @@ def checks(self, service_id, node_meta=None): :rtype: list """ - query_params = {'node-meta': node_meta} if node_meta else {} - return self._get_list(['checks', service_id], query_params) + query_params = {"node-meta": node_meta} if node_meta else {} + return self._get_list(["checks", service_id], query_params) def node(self, node_id): """Return the health info for a given node. @@ -32,7 +32,7 @@ def node(self, node_id): :rtype: list """ - return self._get_list(['node', node_id]) + return self._get_list(["node", node_id]) def service(self, service_id, tag=None, passing=None, node_meta=None): """Returns the nodes and health info of a service @@ -45,14 +45,13 @@ def service(self, service_id, tag=None, passing=None, node_meta=None): query_params = {} if tag: - query_params['tag'] = tag + query_params["tag"] = tag if passing: - query_params['passing'] = '' + query_params["passing"] = "" if node_meta: - query_params['node-meta'] = node_meta + query_params["node-meta"] = node_meta - return self._get_list(['service', service_id], - query_params=query_params) + return self._get_list(["service", service_id], query_params=query_params) def state(self, state): """Returns the checks in a given state where state is one of @@ -62,4 +61,4 @@ def state(self, state): :rtype: list """ - return self._get_list(['state', state]) + return self._get_list(["state", state]) diff --git a/consulate/api/kv.py b/consulate/api/kv.py index ea0403d..0e70788 100644 --- a/consulate/api/kv.py +++ b/consulate/api/kv.py @@ -2,8 +2,8 @@ Consul KV Endpoint Access """ +from consulate import exceptions, utils from consulate.api import base -from consulate import utils, exceptions class KV(base.Endpoint): @@ -36,7 +36,7 @@ def __contains__(self, item): :rtype: bool """ - item = item.lstrip('/') + item = item.lstrip("/") return self._get_no_response_body([item]) def __delitem__(self, item): @@ -58,8 +58,8 @@ def __getitem__(self, item): """ value = self._get_item(item) if not value: - raise KeyError('Key not found ({0})'.format(item)) - return value.get('Value') + raise KeyError("Key not found ({0})".format(item)) + return value.get("Value") def __iter__(self): """Iterate over all the keys in the Key/Value service @@ -103,11 +103,11 @@ def acquire_lock(self, item, session, value=None, cas=None, flags=None): :return: bool """ - query_params = {'acquire': session} + query_params = {"acquire": session} if cas is not None: - query_params['cas'] = cas + query_params["cas"] = cas if flags is not None: - query_params['flags'] = flags + query_params["flags"] = flags return self._put_response_body([item], query_params, value) def delete(self, item, recurse=False): @@ -133,7 +133,7 @@ def get(self, item, default=None, raw=False): """ response = self._get_item(item, raw) if isinstance(response, dict): - return response.get('Value', default) + return response.get("Value", default) return response or default def get_record(self, item): @@ -162,53 +162,53 @@ def find(self, prefix, separator=None): :rtype: dict """ - query_params = {'recurse': None} + query_params = {"recurse": None} if separator: - query_params['keys'] = prefix - query_params['separator'] = separator - response = self._get_list([prefix.lstrip('/')], query_params) + query_params["keys"] = prefix + query_params["separator"] = separator + response = self._get_list([prefix.lstrip("/")], query_params) if separator: results = response else: results = {} for row in response: - results[row['Key']] = row['Value'] + results[row["Key"]] = row["Value"] return results def items(self): """Return a dict of all of the key/value pairs in the Key/Value service - *Example:* + *Example:* - .. code:: python + .. code:: python - >>> consul.kv.items() - {'foo': 'bar', 'bar': 'baz', 'quz': True, 'corgie': 'dog'} + >>> consul.kv.items() + {'foo': 'bar', 'bar': 'baz', 'quz': True, 'corgie': 'dog'} - :rtype: dict + :rtype: dict """ - return [{item['Key']: item['Value']} for item in self._get_all_items()] + return [{item["Key"]: item["Value"]} for item in self._get_all_items()] def iteritems(self): """Iterate over the dict of key/value pairs in the Key/Value service - *Example:* + *Example:* - .. code:: python + .. code:: python - >>> for key, value in consul.kv.iteritems(): - ... print(key, value) - ... - (u'bar', 'baz') - (u'foo', 'bar') - (u'quz', True) + >>> for key, value in consul.kv.iteritems(): + ... print(key, value) + ... + (u'bar', 'baz') + (u'foo', 'bar') + (u'quz', True) - :rtype: iterator + :rtype: iterator """ for item in self._get_all_items(): - yield item['Key'], item['Value'] + yield item["Key"], item["Value"] def keys(self): """Return a list of all of the keys in the Key/Value service @@ -223,7 +223,7 @@ def keys(self): :rtype: list """ - return sorted([row['Key'] for row in self._get_all_items()]) + return sorted([row["Key"] for row in self._get_all_items()]) def records(self, key=None): """Return a list of tuples for all of the records in the Key/Value @@ -243,11 +243,9 @@ def records(self, key=None): """ if key: - return [(item['Key'], item['Flags'], item['Value']) - for item in self._get_list([key], {'recurse': None})] - else: - return [(item['Key'], item['Flags'], item['Value']) - for item in self._get_all_items()] + return [(item["Key"], item["Flags"], item["Value"]) for item in self._get_list([key], {"recurse": None})] + + return [(item["Key"], item["Flags"], item["Value"]) for item in self._get_all_items()] def release_lock(self, item, session): """Release an existing lock from the Consul KV database. @@ -257,7 +255,7 @@ def release_lock(self, item, session): :return: bool """ - return self._put_response_body([item], {'release': session}) + return self._put_response_body([item], {"release": session}) def set(self, item, value): """Set a value in the Key/Value service, using the CAS mechanism @@ -295,7 +293,7 @@ def values(self): :rtype: list """ - return [row['Value'] for row in self._get_all_items()] + return [row["Value"] for row in self._get_all_items()] def _delete_item(self, item, recurse=False): """Remove an item from the Consul database @@ -304,7 +302,7 @@ def _delete_item(self, item, recurse=False): :param recurse: :return: """ - query_params = {'recurse': True} if recurse else {} + query_params = {"recurse": True} if recurse else {} return self._adapter.delete(self._build_uri([item], query_params)) def _get_all_items(self): @@ -314,7 +312,7 @@ def _get_all_items(self): :rtype: list """ - return self._get_list([''], {'recurse': None}) + return self._get_list([""], {"recurse": None}) def _get_item(self, item, raw=False): """Internal method to get the full item record from the Key/Value @@ -325,8 +323,8 @@ def _get_item(self, item, raw=False): :rtype: mixed """ - item = item.lstrip('/') - query_params = {'raw': True} if raw else {} + item = item.lstrip("/") + query_params = {"raw": True} if raw else {} response = self._adapter.get(self._build_uri([item], query_params)) if response.status_code == 200: return response.body @@ -347,8 +345,8 @@ def _get_modify_index(self, item, value, replace): response = self._adapter.get(self._build_uri([item])) index = 0 if response.status_code == 200: - index = response.body.get('ModifyIndex') - rvalue = response.body.get('Value') + index = response.body.get("ModifyIndex") + rvalue = response.body.get("Value") if rvalue == value: return None if not replace: @@ -366,16 +364,11 @@ def _prepare_value(value): if not utils.is_string(value) or isinstance(value, bytes): return value try: - if utils.PYTHON3: - return value.encode('utf-8') - elif isinstance(value, unicode): - return value.encode('utf-8') + return value.encode("utf-8") except UnicodeDecodeError: return value - return value - def _set_item(self, item, value, flags=None, replace=True, - query_params=None): + def _set_item(self, item, value, flags=None, replace=True, query_params=None): """Internal method for setting a key/value pair with flags in the Key/Value service @@ -387,21 +380,19 @@ def _set_item(self, item, value, flags=None, replace=True, """ value = self._prepare_value(value) - if value and item.endswith('/'): - item = item.rstrip('/') + if value and item.endswith("/"): + item = item.rstrip("/") index = self._get_modify_index(item, value, replace) if index is None: return True query_params = query_params or {} - query_params.update({'cas': index}) + query_params.update({"cas": index}) if flags is not None: - query_params['flags'] = flags - response = self._adapter.put(self._build_uri([item], query_params), - value) + query_params["flags"] = flags + response = self._adapter.put(self._build_uri([item], query_params), value) if not response.status_code == 200 or not response.body: if response.status_code == 500: - raise exceptions.ServerError( - response.body or 'Internal Consul server error') - raise KeyError( - 'Error setting "{0}" ({1})'.format(item, response.status_code)) + raise exceptions.ServerError(response.body or "Internal Consul server error") + raise KeyError('Error setting "{0}" ({1})'.format(item, response.status_code)) + return None diff --git a/consulate/api/lock.py b/consulate/api/lock.py index fb6b72e..f470819 100644 --- a/consulate/api/lock.py +++ b/consulate/api/lock.py @@ -6,8 +6,8 @@ import logging import uuid -from consulate.api import base from consulate import exceptions +from consulate.api import base LOGGER = logging.getLogger(__name__) @@ -31,7 +31,8 @@ class Lock(base.Endpoint): :raises: :exc:`~consulate.exception.LockError` """ - DEFAULT_PREFIX = 'consulate/locks' + + DEFAULT_PREFIX = "consulate/locks" def __init__(self, uri, adapter, session, datacenter=None, token=None): """Create a new instance of the Lock @@ -43,8 +44,8 @@ def __init__(self, uri, adapter, session, datacenter=None, token=None): :param str token: Access Token """ - super(Lock, self).__init__(uri, adapter, datacenter, token) - self._base_uri = '{0}/kv'.format(uri) + super().__init__(uri, adapter, datacenter, token) + self._base_uri = "{0}/kv".format(uri) self._session = session self._session_id = None self._item = str(uuid.uuid4()) @@ -79,23 +80,20 @@ def prefix(self, value): :param str value: The value to set the path prefix to """ - self._prefix = value or '' + self._prefix = value or "" def _acquire(self, key=None, value=None): self._session_id = self._session.create() - self._item = '/'.join([self._prefix, (key or str(uuid.uuid4()))]) - LOGGER.debug('Acquiring a lock of %s for session %s', - self._item, self._session_id) - response = self._put_response_body([self._item], - {'acquire': self._session_id}, - value) + self._item = "/".join([self._prefix, (key or str(uuid.uuid4()))]) + LOGGER.debug("Acquiring a lock of %s for session %s", self._item, self._session_id) + response = self._put_response_body([self._item], {"acquire": self._session_id}, value) if not response: self._session.destroy(self._session_id) raise exceptions.LockFailure() def _release(self): """Release the lock""" - self._put_response_body([self._item], {'release': self._session_id}) + self._put_response_body([self._item], {"release": self._session_id}) self._adapter.delete(self._build_uri([self._item])) self._session.destroy(self._session_id) self._item, self._session_id = None, None diff --git a/consulate/api/session.py b/consulate/api/session.py index 97a8a10..a7ffa22 100644 --- a/consulate/api/session.py +++ b/consulate/api/session.py @@ -8,13 +8,7 @@ class Session(base.Endpoint): """Create, destroy, and query Consul sessions.""" - def create(self, - name=None, - behavior='release', - node=None, - delay=None, - ttl=None, - checks=None): + def create(self, name=None, behavior="release", node=None, delay=None, ttl=None, checks=None): """Initialize a new session. None of the fields are mandatory, and in fact no body needs to be PUT @@ -53,18 +47,18 @@ def create(self, :return str: session ID """ - payload = {'name': name} if name else {} + payload = {"name": name} if name else {} if node: - payload['Node'] = node + payload["Node"] = node if behavior: - payload['Behavior'] = behavior + payload["Behavior"] = behavior if delay: - payload['LockDelay'] = delay + payload["LockDelay"] = delay if ttl: - payload['TTL'] = ttl + payload["TTL"] = ttl if checks: - payload['Checks'] = checks - return self._put_response_body(['create'], None, payload).get('ID') + payload["Checks"] = checks + return self._put_response_body(["create"], None, payload).get("ID") def destroy(self, session_id): """Destroy an existing session @@ -73,7 +67,7 @@ def destroy(self, session_id): :return: bool """ - return self._put_no_response_body(['destroy', session_id]) + return self._put_no_response_body(["destroy", session_id]) def info(self, session_id): """Returns the requested session information within a given dc. @@ -83,7 +77,7 @@ def info(self, session_id): :return: dict """ - return self._get_response_body(['info', session_id]) + return self._get_response_body(["info", session_id]) def list(self): """Returns the active sessions for a given dc. @@ -91,7 +85,7 @@ def list(self): :return: list """ - return self._get_response_body(['list']) + return self._get_response_body(["list"]) def node(self, node): """Returns the active sessions for a given node and dc. @@ -101,7 +95,7 @@ def node(self, node): :return: list """ - return self._get_response_body(['node', node]) + return self._get_response_body(["node", node]) def renew(self, session_id): """Renew the given session. This is used with sessions that have a TTL, @@ -112,4 +106,4 @@ def renew(self, session_id): :return: dict """ - return self._put_response_body(['renew', session_id]) + return self._put_response_body(["renew", session_id]) diff --git a/consulate/api/status.py b/consulate/api/status.py index eeb9638..52ecd8d 100644 --- a/consulate/api/status.py +++ b/consulate/api/status.py @@ -17,7 +17,7 @@ def leader(self): :rtype: str """ - return self._get(['leader']) + return self._get(["leader"]) def peers(self): """Get the Raft peers for the datacenter the agent is running in. @@ -25,7 +25,7 @@ def peers(self): :rtype: list """ - value = self._get(['peers']) + value = self._get(["peers"]) if not isinstance(value, list): return [value] return value diff --git a/consulate/cli.py b/consulate/cli.py index e2d12ba..91e463c 100644 --- a/consulate/cli.py +++ b/consulate/cli.py @@ -1,24 +1,23 @@ """Consulate CLI commands""" -# pragma: no cover import argparse import base64 import json -import sys import os -try: - import urlparse -except ImportError: - import urllib.parse as urlparse +import subprocess # nosec +import sys +import time +import urllib.parse as urlparse from requests import exceptions import consulate -from consulate import adapters -from consulate import utils +from consulate import adapters, utils -CONSUL_ENV_VAR = 'CONSUL_RPC_ADDR' -EPILOG = ('If the CONSUL_RPC_ADDR environment variable is set, it will be ' - 'parsed and used for default values when connecting.') +CONSUL_ENV_VAR = "CONSUL_RPC_ADDR" +EPILOG = ( + "If the CONSUL_RPC_ADDR environment variable is set, it will be " + "parsed and used for default values when connecting." +) def on_error(message, exit_code=2): @@ -29,83 +28,94 @@ def on_error(message, exit_code=2): :param int exit_code: The numeric exit code """ - sys.stderr.write(message + '\n') + sys.stderr.write(message + "\n") sys.exit(exit_code) def connection_error(): """Common exit routine when consulate can't connect to Consul""" - on_error('Could not connect to consul', 1) + on_error("Could not connect to consul", 1) ACL_PARSERS = [ - ('backup', 'Backup to stdout or a JSON file', [ - [['-f', '--file'], {'help': 'JSON file to write instead of stdout', - 'nargs': '?'}], - [['-p', '--pretty'], {'help': 'pretty-print JSON output', - 'action': 'store_true'}]]), - ('restore', 'Restore from stdin or a JSON file', [ - [['-f', '--file'], - {'help': 'JSON file to read instead of stdin', - 'nargs': '?'}], - [['-n', '--no-replace'], - {'help': 'Do not replace existing entries', - 'action': 'store_true'}]]) - ] + ( + "backup", + "Backup to stdout or a JSON file", + [ + [["-f", "--file"], {"help": "JSON file to write instead of stdout", "nargs": "?"}], + [["-p", "--pretty"], {"help": "pretty-print JSON output", "action": "store_true"}], + ], + ), + ( + "restore", + "Restore from stdin or a JSON file", + [ + [["-f", "--file"], {"help": "JSON file to read instead of stdin", "nargs": "?"}], + [["-n", "--no-replace"], {"help": "Do not replace existing entries", "action": "store_true"}], + ], + ), +] KV_PARSERS = [ - ('backup', 'Backup to stdout or a JSON file', [ - [['key'], {'help': 'The key to use as target to backup a ' - 'specific key or folder.', - 'nargs': '?'}], - [['-b', '--base64'], {'help': 'Base64 encode values', - 'action': 'store_true'}], - [['-f', '--file'], {'help': 'JSON file to write instead of stdout', - 'nargs': '?'}], - [['-p', '--pretty'], {'help': 'pretty-print JSON output', - 'action': 'store_true'}]]), - ('restore', 'Restore from stdin or a JSON file', [ - [['key'], {'help': 'The key as target to restore to a specific key ' - 'or folder.', - 'nargs': '?'}], - [['-p', '--prune'], {'help': 'Remove entries from consul tree that ' - 'are not in restore file.', - 'action': 'store_true'}], - [['-b', '--base64'], {'help': 'Restore from Base64 encode values', - 'action': 'store_true'}], - [['-f', '--file'], - {'help': 'JSON file to read instead of stdin', - 'nargs': '?'}], - [['-n', '--no-replace'], - {'help': 'Do not replace existing entries', - 'action': 'store_true'}]]), - ('ls', 'List all of the keys', [ - [['key'], {'help': 'The key to use as target to list contents of ' - 'specific key or folder', - 'nargs': '?'}], - [['-l', '--long'], - {'help': 'Long format', - 'action': 'store_true'}]]), - ('mkdir', 'Create a folder', [ - [['path'], - {'help': 'The path to create'}]]), - ('get', 'Get a key from the database', [ - [['key'], {'help': 'The key to get'}], - [['-r', '--recurse'], - {'help': 'Get all keys prefixed with the specified key', - 'action': 'store_true'}], - [['-t', '--trim'], - {'help': 'Number of levels of prefix to trim from returned key', - 'type': int, - 'default': 0}]]), - ('set', 'Set a key in the database', [ - [['key'], {'help': 'The key to set'}], - [['value'], {'help': 'The value of the key'}]]), - ('rm', 'Remove a key from the database', [ - [['key'], {'help': 'The key to remove'}], - [['-r', '--recurse'], - {'help': 'Delete all keys prefixed with the specified key', - 'action': 'store_true'}]])] + ( + "backup", + "Backup to stdout or a JSON file", + [ + [["key"], {"help": "The key to use as target to backup a " "specific key or folder.", "nargs": "?"}], + [["-b", "--base64"], {"help": "Base64 encode values", "action": "store_true"}], + [["-f", "--file"], {"help": "JSON file to write instead of stdout", "nargs": "?"}], + [["-p", "--pretty"], {"help": "pretty-print JSON output", "action": "store_true"}], + ], + ), + ( + "restore", + "Restore from stdin or a JSON file", + [ + [["key"], {"help": "The key as target to restore to a specific key " "or folder.", "nargs": "?"}], + [ + ["-p", "--prune"], + {"help": "Remove entries from consul tree that " "are not in restore file.", "action": "store_true"}, + ], + [["-b", "--base64"], {"help": "Restore from Base64 encode values", "action": "store_true"}], + [["-f", "--file"], {"help": "JSON file to read instead of stdin", "nargs": "?"}], + [["-n", "--no-replace"], {"help": "Do not replace existing entries", "action": "store_true"}], + ], + ), + ( + "ls", + "List all of the keys", + [ + [["key"], {"help": "The key to use as target to list contents of " "specific key or folder", "nargs": "?"}], + [["-l", "--long"], {"help": "Long format", "action": "store_true"}], + ], + ), + ("mkdir", "Create a folder", [[["path"], {"help": "The path to create"}]]), + ( + "get", + "Get a key from the database", + [ + [["key"], {"help": "The key to get"}], + [["-r", "--recurse"], {"help": "Get all keys prefixed with the specified key", "action": "store_true"}], + [ + ["-t", "--trim"], + {"help": "Number of levels of prefix to trim from returned key", "type": int, "default": 0}, + ], + ], + ), + ( + "set", + "Set a key in the database", + [[["key"], {"help": "The key to set"}], [["value"], {"help": "The value of the key"}]], + ), + ( + "rm", + "Remove a key from the database", + [ + [["key"], {"help": "The key to remove"}], + [["-r", "--recurse"], {"help": "Delete all keys prefixed with the specified key", "action": "store_true"}], + ], + ), +] def add_acl_args(parser): @@ -114,10 +124,9 @@ def add_acl_args(parser): :param argparse.Subparser parser: parser """ - kv_parser = parser.add_parser('acl', help='ACL Utilities') + kv_parser = parser.add_parser("acl", help="ACL Utilities") - subparsers = kv_parser.add_subparsers(dest='action', - title='ACL Database Utilities') + subparsers = kv_parser.add_subparsers(dest="action", title="ACL Database Utilities") for (name, help_text, arguments) in ACL_PARSERS: parser = subparsers.add_parser(name, help=help_text) @@ -131,10 +140,9 @@ def add_kv_args(parser): :param argparse.Subparser parser: parser """ - kv_parser = parser.add_parser('kv', help='Key/Value Database Utilities') + kv_parser = parser.add_parser("kv", help="Key/Value Database Utilities") - subparsers = kv_parser.add_subparsers(dest='action', - title='Key/Value Database Utilities') + subparsers = kv_parser.add_subparsers(dest="action", title="Key/Value Database Utilities") for (name, help_text, arguments) in KV_PARSERS: parser = subparsers.add_parser(name, help=help_text) @@ -149,35 +157,22 @@ def add_register_args(parser): """ # Service registration - registerp = parser.add_parser('register', - help='Register a service for this node') - registerp.add_argument('name', help='The service name') - registerp.add_argument('-a', '--address', default=None, - help='Specify an address') - registerp.add_argument('-p', '--port', default=None, type=int, - help='Specify a port') - registerp.add_argument('-s', '--service-id', default=None, - help='Specify a service ID') - registerp.add_argument('-t', '--tags', default=[], - help='Specify a comma delimited list of tags') - rsparsers = registerp.add_subparsers(dest='ctype', - title='Service Check Options') - check = rsparsers.add_parser('check', - help='Define an external script-based check') - check.add_argument('interval', default=10, type=int, - help='How often to run the check script') - check.add_argument('path', default=None, - help='Path to the script invoked by Consul') - httpcheck = rsparsers.add_parser('httpcheck', - help='Define an HTTP-based check') - httpcheck.add_argument('interval', default=10, type=int, - help='How often to run the check script') - httpcheck.add_argument('url', default=None, - help='HTTP URL to be polled by Consul') - rsparsers.add_parser('no-check', help='Do not enable service monitoring') - ttl = rsparsers.add_parser('ttl', help='Define a duration based TTL check') - ttl.add_argument('duration', type=int, default=10, - help='TTL duration for a service with missing check data') + registerp = parser.add_parser("register", help="Register a service for this node") + registerp.add_argument("name", help="The service name") + registerp.add_argument("-a", "--address", default=None, help="Specify an address") + registerp.add_argument("-p", "--port", default=None, type=int, help="Specify a port") + registerp.add_argument("-s", "--service-id", default=None, help="Specify a service ID") + registerp.add_argument("-t", "--tags", default=[], help="Specify a comma delimited list of tags") + rsparsers = registerp.add_subparsers(dest="ctype", title="Service Check Options") + check = rsparsers.add_parser("check", help="Define an external script-based check") + check.add_argument("interval", default=10, type=int, help="How often to run the check script") + check.add_argument("path", default=None, help="Path to the script invoked by Consul") + httpcheck = rsparsers.add_parser("httpcheck", help="Define an HTTP-based check") + httpcheck.add_argument("interval", default=10, type=int, help="How often to run the check script") + httpcheck.add_argument("url", default=None, help="HTTP URL to be polled by Consul") + rsparsers.add_parser("no-check", help="Do not enable service monitoring") + ttl = rsparsers.add_parser("ttl", help="Define a duration based TTL check") + ttl.add_argument("duration", type=int, default=10, help="TTL duration for a service with missing check data") def add_run_once_args(parser): @@ -186,16 +181,10 @@ def add_run_once_args(parser): :param argparse.Subparser parser: parser """ - run_oncep = parser.add_parser('run_once', - help='Run a command locked to a single ' - 'execution') - run_oncep.add_argument('lock', - help='The name of the lock which will be ' - 'held in Consul.') - run_oncep.add_argument('command_to_run', nargs=argparse.REMAINDER, - help='The command to lock') - run_oncep.add_argument('-i', '--interval', default=None, - help='Hold the lock for X seconds') + run_oncep = parser.add_parser("run_once", help="Run a command locked to a single " "execution") + run_oncep.add_argument("lock", help="The name of the lock which will be " "held in Consul.") + run_oncep.add_argument("command_to_run", nargs=argparse.REMAINDER, help="The command to lock") + run_oncep.add_argument("-i", "--interval", default=None, help="Hold the lock for X seconds") def add_deregister_args(parser): @@ -205,9 +194,8 @@ def add_deregister_args(parser): """ # Service registration - registerp = parser.add_parser('deregister', - help='Deregister a service for this node') - registerp.add_argument('service_id', help='The service registration id') + registerp = parser.add_parser("deregister", help="Deregister a service for this node") + registerp.add_argument("service_id", help="The service registration id") def add_services_args(parser): @@ -217,38 +205,30 @@ def add_services_args(parser): """ # Service registration - registerp = parser.add_parser('services', - help='List services for this node') + registerp = parser.add_parser("services", help="List services for this node") + + registerp.add_argument("-i", "--indent", type=int, default=None, help="The indent level for output") - registerp.add_argument('-i', '--indent', type=int, default=None, help='The indent level for output') def parse_cli_args(): """Create the argument parser and add the arguments""" - parser = argparse.ArgumentParser(description='CLI utilities for Consul', - epilog=EPILOG) + parser = argparse.ArgumentParser(description="CLI utilities for Consul", epilog=EPILOG) - env_var = os.environ.get(CONSUL_ENV_VAR, '') + env_var = os.environ.get(CONSUL_ENV_VAR, "") parsed_defaults = urlparse.urlparse(env_var) - parser.add_argument('--api-scheme', - default=parsed_defaults.scheme or 'http', - help='The scheme to use for connecting to Consul with') - parser.add_argument('--api-host', - default=parsed_defaults.hostname or 'localhost', - help='The consul host to connect on') - parser.add_argument('--api-port', - default=parsed_defaults.port or 8500, - help='The consul API port to connect to') - parser.add_argument('--datacenter', - dest='dc', - default=None, - help='The datacenter to specify for the connection') - parser.add_argument('--token', default=None, help='ACL token') - parser.add_argument('--version', action='version', - version=consulate.__version__, - help='Current consulate version') - - sparser = parser.add_subparsers(title='Commands', dest='command') + parser.add_argument( + "--api-scheme", default=parsed_defaults.scheme or "http", help="The scheme to use for connecting to Consul with" + ) + parser.add_argument( + "--api-host", default=parsed_defaults.hostname or "localhost", help="The consul host to connect on" + ) + parser.add_argument("--api-port", default=parsed_defaults.port or 8500, help="The consul API port to connect to") + parser.add_argument("--datacenter", dest="dc", default=None, help="The datacenter to specify for the connection") + parser.add_argument("--token", default=None, help="ACL token") + parser.add_argument("--version", action="version", version=consulate.__version__, help="Current consulate version") + + sparser = parser.add_subparsers(title="Commands", dest="command") add_acl_args(sparser) add_kv_args(sparser) add_register_args(sparser) @@ -265,14 +245,13 @@ def acl_backup(consul, args): :param argparser.namespace args: The cli args """ - handle = open(args.file, 'w') if args.file else sys.stdout + handle = open(args.file, "w") if args.file else sys.stdout acls = consul.acl.list() try: if args.pretty: - handle.write(json.dumps(acls, sort_keys=True, indent=2, - separators=(',', ': ')) + '\n') + handle.write(json.dumps(acls, sort_keys=True, indent=2, separators=(",", ": ")) + "\n") else: - handle.write(json.dumps(acls, sort_keys=True) + '\n') + handle.write(json.dumps(acls, sort_keys=True) + "\n") except exceptions.ConnectionError: connection_error() @@ -284,17 +263,14 @@ def acl_restore(consul, args): :param argparser.namespace args: The cli args """ - handle = open(args.file, 'r') if args.file else sys.stdin + handle = open(args.file, "r") if args.file else sys.stdin data = json.load(handle) for row in data: - consul.acl.update(row['ID'], row['Name'], row['Type'], row['Rules']) - print('{0} ACLs written'.format(len(data))) + consul.acl.update(row["ID"], row["Name"], row["Type"], row["Rules"]) + print("{0} ACLs written".format(len(data))) -ACL_ACTIONS = { - 'backup': acl_backup, - 'restore': acl_restore -} +ACL_ACTIONS = {"backup": acl_backup, "restore": acl_restore} def kv_backup(consul, args): @@ -304,28 +280,20 @@ def kv_backup(consul, args): :param argparser.namespace args: The cli args """ - handle = open(args.file, 'w') if args.file else sys.stdout + handle = open(args.file, "w") if args.file else sys.stdout if args.key: - args.key = args.key.strip('/') - prefixlen = len(args.key.split('/')) - records = [('/'.join(k.split('/')[prefixlen:]), f, v) - for k, f, v in consul.kv.records(args.key)] + args.key = args.key.strip("/") + prefixlen = len(args.key.split("/")) + records = [("/".join(k.split("/")[prefixlen:]), f, v) for k, f, v in consul.kv.records(args.key)] else: records = consul.kv.records() if args.base64: - if utils.PYTHON3: - records = [(k, f, str(base64.b64encode(utils.maybe_encode(v)), - 'ascii') if v else v) - for k, f, v in records] - else: - records = [(k, f, base64.b64encode(v) if v else v) - for k, f, v in records] + records = [(k, f, str(base64.b64encode(utils.maybe_encode(v)), "ascii") if v else v) for k, f, v in records] try: if args.pretty: - handle.write(json.dumps(records, sort_keys=True, indent=2, - separators=(',', ': ')) + '\n') + handle.write(json.dumps(records, sort_keys=True, indent=2, separators=(",", ": ")) + "\n") else: - handle.write(json.dumps(records) + '\n') + handle.write(json.dumps(records) + "\n") except exceptions.ConnectionError: connection_error() @@ -355,14 +323,14 @@ def kv_get(consul, args): for key in sorted(consul.kv.find(args.key)): displaykey = key if args.trim: - keyparts = displaykey.split('/') - if (args.trim >= len(keyparts)): + keyparts = displaykey.split("/") + if args.trim >= len(keyparts): displaykey = keyparts[-1] else: - displaykey = '/'.join(keyparts[args.trim:]) - sys.stdout.write('%s\t%s\n' % (displaykey, consul.kv.get(key))) + displaykey = "/".join(keyparts[args.trim :]) # noqa: E203 + sys.stdout.write("%s\t%s\n" % (displaykey, consul.kv.get(key))) else: - sys.stdout.write('%s\n' % consul.kv.get(args.key)) + sys.stdout.write("%s\n" % consul.kv.get(args.key)) except exceptions.ConnectionError: connection_error() @@ -376,7 +344,7 @@ def kv_ls(consul, args): """ try: if args.key: - args.key = args.key.lstrip('/') + args.key = args.key.lstrip("/") keylist = sorted(consul.kv.find(args.key)) else: keylist = consul.kv.keys() @@ -385,7 +353,7 @@ def kv_ls(consul, args): keylen = 0 if consul.kv[key]: keylen = len(consul.kv[key]) - print('{0:>14} {1}'.format(keylen, key)) + print("{0:>14} {1}".format(keylen, key)) else: print(key) except exceptions.ConnectionError: @@ -399,15 +367,15 @@ def kv_mkdir(consul, args): :param argparser.namespace args: The cli args """ - if not args.path[:-1] == '/': - args.path += '/' + if not args.path[:-1] == "/": + args.path += "/" try: consul.kv.set(args.path, None) except exceptions.ConnectionError: connection_error() -def kv_restore(consul, args): +def kv_restore(consul, args): # pylint: disable=too-many-branches,too-many-format-args; # noqa: C901 """Restore the Consul KV store :param consulate.api_old.Consul consul: The Consul instance @@ -416,30 +384,28 @@ def kv_restore(consul, args): """ if args.prune: if args.key: - args.key = args.key.strip('/') + args.key = args.key.strip("/") keylist = consul.kv.find(args.key) else: - keylist = consul.kv.find('') - handle = open(args.file, 'r') if args.file else sys.stdin + keylist = consul.kv.find("") + handle = open(args.file, "r") if args.file else sys.stdin data = json.load(handle) for row in data: if isinstance(row, dict): # translate raw api export to internal representation - if row['Value'] is not None: - row['Value'] = base64.b64decode(row['Value']) - row = [row['Key'], row['Flags'], row['Value']] + if row["Value"] is not None: + row["Value"] = base64.b64decode(row["Value"]) + row = [row["Key"], row["Flags"], row["Value"]] if args.base64 and row[2] is not None: row[2] = base64.b64decode(row[2]) # Here's an awesome thing to make things work - if not utils.PYTHON3 and isinstance(row[2], unicode): - row[2] = row[2].encode('utf-8') if args.key: if row[0] == "": rowkey = args.key else: - rowkey = args.key + '/' + row[0] + rowkey = args.key + "/" + row[0] else: rowkey = row[0] if args.prune: @@ -486,14 +452,15 @@ def kv_set(consul, args): # Mapping dict to simplify the code in main() KV_ACTIONS = { - 'backup': kv_backup, - 'del': kv_delete, - 'get': kv_get, - 'ls': kv_ls, - 'mkdir': kv_mkdir, - 'restore': kv_restore, - 'rm': kv_rm, - 'set': kv_set} + "backup": kv_backup, + "del": kv_delete, + "get": kv_get, + "ls": kv_ls, + "mkdir": kv_mkdir, + "restore": kv_restore, + "rm": kv_rm, + "set": kv_set, +} def register(consul, args): @@ -503,16 +470,15 @@ def register(consul, args): :param argparser.namespace args: The cli args """ - check = args.path if args.ctype == 'check' else None - httpcheck = args.url if args.ctype == 'httpcheck' else None - interval = '%ss' % args.interval if args.ctype in ['check', - 'httpcheck'] else None - ttl = '%ss' % args.duration if args.ctype == 'ttl' else None - tags = args.tags.split(',') if args.tags else None + check = args.path if args.ctype == "check" else None + httpcheck = args.url if args.ctype == "httpcheck" else None + interval = "%ss" % args.interval if args.ctype in ["check", "httpcheck"] else None + ttl = "%ss" % args.duration if args.ctype == "ttl" else None + tags = args.tags.split(",") if args.tags else None try: - consul.agent.service.register(args.name, args.service_id, args.address, - int(args.port), tags, check, interval, - ttl, httpcheck) + consul.agent.service.register( + args.name, args.service_id, args.address, int(args.port), tags, check, interval, ttl, httpcheck + ) except exceptions.ConnectionError: connection_error() @@ -537,47 +503,41 @@ def run_once(consul, args): :param argparser.namespace args: The cli args """ - import time - import subprocess error_msg, error_code = None, None try: - consul.lock.prefix('') + consul.lock.prefix("") with consul.lock.acquire(args.lock): if args.interval: now = int(time.time()) last_run = consul.kv.get("{0}_last_run".format(args.lock)) - if str(last_run) not in ['null', 'None'] and \ - int(last_run) + int(args.interval) > now: - sys.stdout.write( - 'Last run happened fewer than {0} seconds ago. ' - 'Exiting\n'.format(args.interval)) + if str(last_run) not in ["null", "None"] and int(last_run) + int(args.interval) > now: + sys.stdout.write("Last run happened fewer than {0} seconds ago. " "Exiting\n".format(args.interval)) return consul.kv["{0}_last_run".format(args.lock)] = now # Should the subprocess return an error code, release the lock try: - print(subprocess.check_output(args.command_to_run[0].strip(), - stderr=subprocess.STDOUT, - shell=True)) + print( + subprocess.check_output( + args.command_to_run[0].strip(), stderr=subprocess.STDOUT, shell=True # nosec + ) + ) # If the subprocess fails except subprocess.CalledProcessError as err: error_code = 1 - error_msg = ('"{0}" exited with return code "{1}" and ' - 'output {2}'.format(args.command_to_run, - err.returncode, - err.output)) + error_msg = '"{0}" exited with return code "{1}" and ' "output {2}".format( + args.command_to_run, err.returncode, err.output + ) except OSError as err: error_code = 1 - error_msg = '"{0}" command does not exist'.format( - args.command_to_run, err) + error_msg = '"{0}" command does not exist "{1}"'.format(args.command_to_run, err) except Exception as err: error_code = 1 - error_msg = '"{0}" exited with error "{1}"'.format( - args.command_to_run, err) + error_msg = '"{0}" exited with error "{1}"'.format(args.command_to_run, err) except consulate.LockFailure: - on_error('Cannot obtain the required lock. Exiting') + on_error("Cannot obtain the required lock. Exiting") except exceptions.ConnectionError: connection_error() @@ -595,43 +555,39 @@ def services(consul, args): """ svcs = consul.agent.services() - print(json.dumps(svcs, - sort_keys=True, - indent=args.indent, - separators=(',', ': ')) + '\n') + print(json.dumps(svcs, sort_keys=True, indent=args.indent, separators=(",", ": ")) + "\n") def main(): """Entrypoint for the consulate cli application""" args = parse_cli_args() - if args.api_scheme == 'http+unix': + if args.api_scheme == "http+unix": adapter = adapters.UnixSocketRequest port = None - api_host = os.environ.get('CONSUL_HTTP_ADDR').replace('unix://', '') + api_host = os.environ.get("CONSUL_HTTP_ADDR").replace("unix://", "") if args.api_host: api_host = args.api_host else: adapter = None port = args.api_port - api_host = 'localhost' + api_host = "localhost" if args.api_host: api_host = args.api_host - consul = consulate.Consul(api_host, port, args.dc, - args.token, args.api_scheme, adapter) + consul = consulate.Consul(api_host, port, args.dc, args.token, args.api_scheme, adapter) - if args.command == 'acl': + if args.command == "acl": ACL_ACTIONS[args.action](consul, args) - elif args.command == 'kv': + elif args.command == "kv": KV_ACTIONS[args.action](consul, args) - elif args.command == 'register': + elif args.command == "register": register(consul, args) - elif args.command == 'deregister': + elif args.command == "deregister": deregister(consul, args) - elif args.command == 'services': + elif args.command == "services": services(consul, args) - elif args.command == 'run_once': + elif args.command == "run_once": run_once(consul, args) diff --git a/consulate/client.py b/consulate/client.py index bf8e8fb..b53d029 100644 --- a/consulate/client.py +++ b/consulate/client.py @@ -3,17 +3,19 @@ """ import os -from consulate import adapters, api, utils +from urllib.parse import quote -DEFAULT_HOST = os.environ.get('CONSUL_HOST') or 'localhost' -DEFAULT_PORT = os.environ.get('CONSUL_PORT') or 8500 -DEFAULT_ADDR = os.environ.get('CONSUL_HTTP_ADDR') -DEFAULT_SCHEME = 'http' -DEFAULT_TOKEN = os.environ.get('CONSUL_HTTP_TOKEN') -API_VERSION = 'v1' +from consulate import adapters, api +DEFAULT_HOST = os.environ.get("CONSUL_HOST") or "localhost" +DEFAULT_PORT = os.environ.get("CONSUL_PORT") or 8500 +DEFAULT_ADDR = os.environ.get("CONSUL_HTTP_ADDR") +DEFAULT_SCHEME = "http" +DEFAULT_TOKEN = os.environ.get("CONSUL_HTTP_TOKEN") +API_VERSION = "v1" -class Consul(object): + +class Consul: # pylint: disable=too-many-instance-attributes """Access the Consul HTTP API via Python. The default values connect to Consul via ``localhost:8500`` via http. If @@ -39,36 +41,33 @@ class Consul(object): :param float timeout: Timeout in seconds for API requests (Default: None) """ - def __init__(self, - addr=DEFAULT_ADDR, - host=DEFAULT_HOST, - port=DEFAULT_PORT, - datacenter=None, - token=DEFAULT_TOKEN, - scheme=DEFAULT_SCHEME, - adapter=None, - verify=True, - cert=None, - timeout=None): + + def __init__( + self, + addr=DEFAULT_ADDR, + host=DEFAULT_HOST, + port=DEFAULT_PORT, + datacenter=None, + token=DEFAULT_TOKEN, + scheme=DEFAULT_SCHEME, + adapter=None, + verify=True, + cert=None, + timeout=None, + ): # pylint: disable=too-many-arguments """Create a new instance of the Consul class""" - base_uri = self._base_uri(addr=addr, - scheme=scheme, - host=host, - port=port) - self._adapter = adapter() if adapter else adapters.Request( - timeout=timeout, verify=verify, cert=cert) + base_uri = self._base_uri(addr=addr, scheme=scheme, host=host, port=port) + self._adapter = adapter() if adapter else adapters.Request(timeout=timeout, verify=verify, cert=cert) self._acl = api.ACL(base_uri, self._adapter, datacenter, token) self._agent = api.Agent(base_uri, self._adapter, datacenter, token) self._catalog = api.Catalog(base_uri, self._adapter, datacenter, token) self._event = api.Event(base_uri, self._adapter, datacenter, token) self._health = api.Health(base_uri, self._adapter, datacenter, token) - self._coordinate = api.Coordinate(base_uri, self._adapter, datacenter, - token) + self._coordinate = api.Coordinate(base_uri, self._adapter, datacenter, token) self._kv = api.KV(base_uri, self._adapter, datacenter, token) self._session = api.Session(base_uri, self._adapter, datacenter, token) self._status = api.Status(base_uri, self._adapter, datacenter, token) - self._lock = api.Lock(base_uri, self._adapter, self._session, - datacenter, token) + self._lock = api.Lock(base_uri, self._adapter, self._session, datacenter, token) @property def acl(self): @@ -193,8 +192,6 @@ def _base_uri(scheme, host, port, addr=None): """ if addr is None: if port: - return '{0}://{1}:{2}/{3}'.format(scheme, host, port, - API_VERSION) - return '{0}://{1}/{2}'.format(scheme, utils.quote(host, ''), - API_VERSION) - return '{0}/{1}'.format(addr, API_VERSION) + return "{0}://{1}:{2}/{3}".format(scheme, host, port, API_VERSION) + return "{0}://{1}/{2}".format(scheme, quote(host, ""), API_VERSION) + return "{0}/{1}".format(addr, API_VERSION) diff --git a/consulate/models/acl.py b/consulate/models/acl.py index 3c334fe..690d985 100644 --- a/consulate/models/acl.py +++ b/consulate/models/acl.py @@ -1,139 +1,147 @@ -# coding=utf-8 """Models for the ACL endpoints""" import uuid from consulate.models import base -def _validate_link_array(value, model): - """ Validate the policies or roles links are formatted correctly. +def _validate_link_array(value, model): # pylint: disable=unused-argument + """Validate the policies or roles links are formatted correctly. :param list value: An array of PolicyLink or RoleLink. :param rtype: bool """ - return all(['ID' in link or 'Name' in link for link in value]) + return all(["ID" in link or "Name" in link for link in value]) -def _validate_service_identities(value, model): - """ Validate service_identities is formatted correctly. +def _validate_service_identities(value, model): # pylint: disable=unused-argument + """Validate service_identities is formatted correctly. :param ServiceIdentities value: A ServiceIdentity list :param rtype: bool """ - return all( - ['ServiceName' in service_identity for service_identity in value]) + return all(["ServiceName" in service_identity for service_identity in value]) class ACLPolicy(base.Model): """Defines the model used for an ACL policy.""" - __slots__ = ['datacenters', 'description', 'id', 'name', 'rules'] + + __slots__ = ["datacenters", "description", "id", "name", "rules"] __attributes__ = { - 'datacenters': { - 'key': 'Datacenters', - 'type': list, - }, - 'description': { - 'key': 'Description', - 'type': str, - }, - 'id': { - 'key': 'ID', - 'type': uuid.UUID, - 'cast_from': str, - 'cast_to': str, - }, - 'name': { - 'key': 'Name', - 'type': str, - }, - 'rules': { - 'key': 'Rules', - 'type': str, - } + "datacenters": { + "key": "Datacenters", + "type": list, + }, + "description": { + "key": "Description", + "type": str, + }, + "id": { + "key": "ID", + "type": uuid.UUID, + "cast_from": str, + "cast_to": str, + }, + "name": { + "key": "Name", + "type": str, + }, + "rules": { + "key": "Rules", + "type": str, + }, } class ACLRole(base.Model): """Defines the model used for an ACL role.""" - __slots__ = ['description', 'name', 'policies', 'service_identities'] + + __slots__ = ["description", "name", "policies", "service_identities"] __attributes__ = { - 'description': { - 'key': 'Description', - 'type': str, + "description": { + "key": "Description", + "type": str, }, - 'name': { - 'key': 'Name', - 'type': str, - 'required': True, + "name": { + "key": "Name", + "type": str, + "required": True, }, - 'policies': { - 'key': 'Policies', - 'type': list, - 'validator': _validate_link_array, + "policies": { + "key": "Policies", + "type": list, + "validator": _validate_link_array, }, "service_identities": { - 'key': 'ServiceIdentities', - 'type': list, - 'validator': _validate_service_identities, - } + "key": "ServiceIdentities", + "type": list, + "validator": _validate_service_identities, + }, } class ACLToken(base.Model): """Defines the model used for an ACL token.""" + __slots__ = [ - 'accessor_id', 'description', 'expiration_time', 'expiration_ttl', - 'local', 'policies', 'roles', 'secret_id', 'service_identities' + "accessor_id", + "description", + "expiration_time", + "expiration_ttl", + "local", + "policies", + "roles", + "secret_id", + "service_identities", ] __attributes__ = { - 'accessor_id': { - 'key': 'AccessorID', - 'type': uuid.UUID, - 'cast_from': str, - 'cast_to': str, - }, - 'description': { - 'key': 'Description', - 'type': str, - }, - 'expiration_time': { - 'key': 'ExpirationTime', - 'type': str, - }, - 'expiration_ttl': { - 'key': 'ExpirationTTL', - 'type': str, - }, - 'local': { - 'key': 'Local', - 'type': bool, - }, - 'policies': { - 'key': 'Policies', - 'type': list, - 'validator': _validate_link_array, - }, - 'roles': { - 'key': 'Roles', - 'type': list, - 'validator': _validate_link_array, - }, - 'secret_id': { - 'key': 'SecretID', - 'type': uuid.UUID, - 'cast_from': str, - 'cast_to': str, + "accessor_id": { + "key": "AccessorID", + "type": uuid.UUID, + "cast_from": str, + "cast_to": str, + }, + "description": { + "key": "Description", + "type": str, + }, + "expiration_time": { + "key": "ExpirationTime", + "type": str, + }, + "expiration_ttl": { + "key": "ExpirationTTL", + "type": str, + }, + "local": { + "key": "Local", + "type": bool, + }, + "policies": { + "key": "Policies", + "type": list, + "validator": _validate_link_array, + }, + "roles": { + "key": "Roles", + "type": list, + "validator": _validate_link_array, + }, + "secret_id": { + "key": "SecretID", + "type": uuid.UUID, + "cast_from": str, + "cast_to": str, }, "service_identities": { - 'key': 'ServiceIdentities', - 'type': list, - 'validator': _validate_service_identities, - } + "key": "ServiceIdentities", + "type": list, + "validator": _validate_service_identities, + }, } @@ -142,27 +150,12 @@ class ACLToken(base.Model): class ACL(base.Model): """Defines the model used for an individual ACL token.""" - __slots__ = ['id', 'name', 'type', 'rules'] + + __slots__ = ["id", "name", "type", "rules"] __attributes__ = { - 'id': { - 'key': 'ID', - 'type': uuid.UUID, - 'cast_from': str, - 'cast_to': str - }, - 'name': { - 'key': 'Name', - 'type': str - }, - 'type': { - 'key': 'Type', - 'type': str, - 'enum': {'client', 'management'}, - 'required': True - }, - 'rules': { - 'key': 'Rules', - 'type': str - } + "id": {"key": "ID", "type": uuid.UUID, "cast_from": str, "cast_to": str}, + "name": {"key": "Name", "type": str}, + "type": {"key": "Type", "type": str, "enum": {"client", "management"}, "required": True}, + "rules": {"key": "Rules", "type": str}, } diff --git a/consulate/models/agent.py b/consulate/models/agent.py index df34d51..6decd09 100644 --- a/consulate/models/agent.py +++ b/consulate/models/agent.py @@ -1,7 +1,6 @@ -# coding=utf-8 """Models for the Agent endpoints""" -from consulate.models import base from consulate import utils +from consulate.models import base def _validate_args(value, model): @@ -13,9 +12,8 @@ def _validate_args(value, model): :rtype: bool """ - return all([isinstance(v, str) for v in value]) \ - and not model.args and not model.grpc and not model.http \ - and not model.ttl + is_instance = all([isinstance(v, str) for v in value]) + return is_instance and not model.args and not model.grpc and not model.http and not model.ttl def _validate_grpc(value, model): @@ -27,9 +25,7 @@ def _validate_grpc(value, model): :rtype: bool """ - return utils.validate_url(value) \ - and not model.args and not model.http \ - and not model.tcp and not model.ttl + return utils.validate_url(value) and not model.args and not model.http and not model.tcp and not model.ttl def _validate_http(value, model): @@ -41,9 +37,7 @@ def _validate_http(value, model): :rtype: bool """ - return utils.validate_url(value) \ - and not model.args and not model.grpc and not model.tcp \ - and not model.ttl + return utils.validate_url(value) and not model.args and not model.grpc and not model.tcp and not model.ttl def _validate_interval(value, model): @@ -65,8 +59,7 @@ def _validate_tcp(_value, model): :rtype: bool """ - return not model.args and not model.grpc \ - and not model.http and not model.ttl + return not model.args and not model.grpc and not model.http and not model.ttl def _validate_ttl(value, model): @@ -77,170 +70,92 @@ def _validate_ttl(value, model): :rtype: bool """ - return utils.validate_go_interval(value) and not model.args \ - and not model.grpc and not model.http \ - and not model.tcp and not model.interval + is_valid = utils.validate_go_interval(value) + return is_valid and not model.args and not model.grpc and not model.http and not model.tcp and not model.interval class Check(base.Model): """Model for making Check API requests to Consul.""" - __slots__ = ['id', 'name', 'interval', 'notes', - 'deregister_critical_service_after', 'args', - 'docker_container_id', 'grpc', 'grpc_use_tls', - 'http', 'method', 'header', 'timeout', 'tls_skip_verify', - 'tcp', 'ttl', 'service_id', 'status'] + __slots__ = [ + "id", + "name", + "interval", + "notes", + "deregister_critical_service_after", + "args", + "docker_container_id", + "grpc", + "grpc_use_tls", + "http", + "method", + "header", + "timeout", + "tls_skip_verify", + "tcp", + "ttl", + "service_id", + "status", + ] __attributes__ = { - 'id': { - 'key': 'ID', - 'type': str - }, - 'name': { - 'key': 'Name', - 'type': str, - 'required': True - }, - 'interval': { - 'key': 'Interval', - 'type': str, - 'validator': _validate_interval - }, - 'notes': { - 'key': 'Notes', - 'type': str - }, - 'deregister_critical_service_after': { - 'key': 'DeregisterCriticalServiceAfter', - 'type': str, - 'validator': utils.validate_go_interval - }, - 'args': { - 'key': 'Args', - 'type': list, - 'validator': _validate_args - }, - 'docker_container_id': { - 'key': 'DockerContainerID', - 'type': str - }, - 'grpc': { - 'key': 'GRPC', - 'type': str, - 'validator': _validate_grpc - }, - 'grpc_use_tls': { - 'key': 'GRPCUseTLS', - 'type': bool - }, - 'http': { - 'key': 'HTTP', - 'type': str, - 'validator': _validate_http - }, - 'method': { - 'key': 'Method', - 'type': str, - 'enum': { - 'HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'TRACE' - } - }, - 'header': { - 'key': 'Header', - 'type': dict, - 'validator': lambda h, _m: all( - [(isinstance(k, str) and isinstance(v, str)) - for k, v in h.items()]), - 'cast_to': lambda h: {k: [v] for k, v in h.items()} - }, - 'timeout': { - 'key': 'Timeout', - 'type': str, - 'validator': utils.validate_go_interval - }, - 'tls_skip_verify': { - 'key': 'TLSSkipVerify', - 'type': bool - }, - 'tcp': { - 'key': 'TCP', - 'type': str, - 'validator': _validate_tcp - }, - 'ttl': { - 'key': 'TTL', - 'type': str, - 'validator': _validate_ttl - }, - 'service_id': { - 'key': 'ServiceID', - 'type': str - }, - 'status': { - 'key': 'Status', - 'type': str, - 'enum': {'passing', 'warning', 'critical', 'maintenance'} - } + "id": {"key": "ID", "type": str}, + "name": {"key": "Name", "type": str, "required": True}, + "interval": {"key": "Interval", "type": str, "validator": _validate_interval}, + "notes": {"key": "Notes", "type": str}, + "deregister_critical_service_after": { + "key": "DeregisterCriticalServiceAfter", + "type": str, + "validator": utils.validate_go_interval, + }, + "args": {"key": "Args", "type": list, "validator": _validate_args}, + "docker_container_id": {"key": "DockerContainerID", "type": str}, + "grpc": {"key": "GRPC", "type": str, "validator": _validate_grpc}, + "grpc_use_tls": {"key": "GRPCUseTLS", "type": bool}, + "http": {"key": "HTTP", "type": str, "validator": _validate_http}, + "method": {"key": "Method", "type": str, "enum": {"HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS", "TRACE"}}, + "header": { + "key": "Header", + "type": dict, + "validator": lambda h, _m: all([(isinstance(k, str) and isinstance(v, str)) for k, v in h.items()]), + "cast_to": lambda h: {k: [v] for k, v in h.items()}, + }, + "timeout": {"key": "Timeout", "type": str, "validator": utils.validate_go_interval}, + "tls_skip_verify": {"key": "TLSSkipVerify", "type": bool}, + "tcp": {"key": "TCP", "type": str, "validator": _validate_tcp}, + "ttl": {"key": "TTL", "type": str, "validator": _validate_ttl}, + "service_id": {"key": "ServiceID", "type": str}, + "status": {"key": "Status", "type": str, "enum": {"passing", "warning", "critical", "maintenance"}}, } def __init__(self, **kwargs): - super(Check, self).__init__(**kwargs) - if (self.args or self.grpc or self.http or self.tcp) \ - and not self.interval: - raise ValueError('"interval" must be specified when specifying ' - 'args, grpc, http, or tcp.') + super().__init__(**kwargs) + if (self.args or self.grpc or self.http or self.tcp) and not self.interval: + raise ValueError('"interval" must be specified when specifying ' "args, grpc, http, or tcp.") class Service(base.Model): """Model for making Check API requests to Consul.""" - __slots__ = ['id', 'name', 'tags', 'meta', 'address', 'port', 'check', - 'checks', 'enable_tag_override'] + __slots__ = ["id", "name", "tags", "meta", "address", "port", "check", "checks", "enable_tag_override"] __attributes__ = { - 'id': { - 'key': 'ID', - 'type': str - }, - 'name': { - 'key': 'Name', - 'type': str, - 'required': True - }, - 'tags': { - 'key': 'Tags', - 'type': list, - 'validator': lambda t, _m: all([isinstance(v, str) for v in t]) - }, - 'meta': { - 'key': 'Meta', - 'type': dict, - 'validator': lambda h, _m: all( - [(isinstance(k, str) and isinstance(v, str)) - for k, v in h.items()]), - }, - 'address': { - 'key': 'Address', - 'type': str - }, - 'port': { - 'key': 'Port', - 'type': int - }, - 'check': { - 'key': 'Check', - 'type': Check, - 'cast_to': dict - }, - 'checks': { - 'key': 'Checks', - 'type': list, - 'validator': lambda c, _m: all([isinstance(v, Check) for v in c]), - 'cast_to': lambda c: [dict(check) for check in c] - }, - 'enable_tag_override': { - 'Key': 'EnableTagOverride', - 'type': bool - } + "id": {"key": "ID", "type": str}, + "name": {"key": "Name", "type": str, "required": True}, + "tags": {"key": "Tags", "type": list, "validator": lambda t, _m: all([isinstance(v, str) for v in t])}, + "meta": { + "key": "Meta", + "type": dict, + "validator": lambda h, _m: all([(isinstance(k, str) and isinstance(v, str)) for k, v in h.items()]), + }, + "address": {"key": "Address", "type": str}, + "port": {"key": "Port", "type": int}, + "check": {"key": "Check", "type": Check, "cast_to": dict}, + "checks": { + "key": "Checks", + "type": list, + "validator": lambda c, _m: all([isinstance(v, Check) for v in c]), + "cast_to": lambda c: [dict(check) for check in c], + }, + "enable_tag_override": {"Key": "EnableTagOverride", "type": bool}, } - diff --git a/consulate/models/base.py b/consulate/models/base.py index 50802bf..172c61c 100644 --- a/consulate/models/base.py +++ b/consulate/models/base.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Base Model @@ -37,10 +36,11 @@ class MyModel(Model): """The attributes that define the data elements of the model""" def __init__(self, **kwargs): - super(Model, self).__init__() - [setattr(self, name, value) for name, value in kwargs.items()] - [self._set_default(name) for name in self.__attributes__.keys() - if name not in kwargs.keys()] + super().__init__() + [setattr(self, name, value) for name, value in kwargs.items()] # pylint: disable=expression-not-assigned + [ # pylint: disable=expression-not-assigned + self._set_default(name) for name in self.__attributes__ if name not in kwargs + ] def __iter__(self): """Iterate through the model's key, value pairs. @@ -48,7 +48,7 @@ def __iter__(self): :rtype: iterator """ - for name in self.__attributes__.keys(): + for name in self.__attributes__: value = self._maybe_cast_value(name) if value is not None: yield self._maybe_return_key(name), value @@ -67,7 +67,7 @@ def __setattr__(self, name, value): if name not in self.__attributes__: raise AttributeError('Invalid attribute "{}"'.format(name)) value = self._validate_value(name, value) - super(Model, self).__setattr__(name, value) + super().__setattr__(name, value) def __getattribute__(self, name): """Return the attribute from the model if it is set, otherwise @@ -78,10 +78,10 @@ def __getattribute__(self, name): """ try: - return super(Model, self).__getattribute__(name) + return super().__getattribute__(name) except AttributeError: if name in self.__attributes__: - return self.__attributes__[name].get('default', None) + return self.__attributes__[name].get("default", None) raise def _maybe_cast_value(self, name): @@ -93,8 +93,8 @@ def _maybe_cast_value(self, name): """ value = getattr(self, name) - if value is not None and self.__attributes__[name].get('cast_to'): - return self.__attributes__[name]['cast_to'](value) + if value is not None and self.__attributes__[name].get("cast_to"): + return self.__attributes__[name]["cast_to"](value) return value def _maybe_return_key(self, name): @@ -106,8 +106,8 @@ def _maybe_return_key(self, name): :rtype: mixed """ - if self.__attributes__[name].get('key'): - return self.__attributes__[name]['key'] + if self.__attributes__[name].get("key"): + return self.__attributes__[name]["key"] return name def _required_attr(self, name): @@ -117,7 +117,7 @@ def _required_attr(self, name): :rtype: bool """ - return self.__attributes__[name].get('required', False) + return self.__attributes__[name].get("required", False) def _set_default(self, name): """Set the default value for the attribute name. @@ -125,7 +125,7 @@ def _set_default(self, name): :param str name: The attribute name """ - setattr(self, name, self.__attributes__[name].get('default', None)) + setattr(self, name, self.__attributes__[name].get("default", None)) def _validate_value(self, name, value): """Ensures the the value validates based upon the type or a validation @@ -141,27 +141,24 @@ def _validate_value(self, name, value): if value is None: if self._required_attr(name): raise ValueError('Attribute "{}" is required'.format(name)) - return + return None - if not isinstance(value, self.__attributes__[name].get('type')): - cast_from = self.__attributes__[name].get('cast_from') + if not isinstance(value, self.__attributes__[name].get("type")): + cast_from = self.__attributes__[name].get("cast_from") if cast_from and isinstance(value, cast_from): - value = self.__attributes__[name]['type'](value) + value = self.__attributes__[name]["type"](value) else: raise TypeError( 'Attribute "{}" must be of type {} not {}'.format( - name, self.__attributes__[name]['type'].__name__, - value.__class__.__name__)) + name, self.__attributes__[name]["type"].__name__, value.__class__.__name__ + ) + ) - if self.__attributes__[name].get('enum') \ - and value not in self.__attributes__[name]['enum']: - raise ValueError( - 'Attribute "{}" value {!r} not valid'.format(name, value)) + if self.__attributes__[name].get("enum") and value not in self.__attributes__[name]["enum"]: + raise ValueError('Attribute "{}" value {!r} not valid'.format(name, value)) - validator = self.__attributes__[name].get('validator') + validator = self.__attributes__[name].get("validator") if callable(validator): if not validator(value, self): - raise ValueError( - 'Attribute "{}" value {!r} did not validate'.format( - name, value)) + raise ValueError('Attribute "{}" value {!r} did not validate'.format(name, value)) return value diff --git a/consulate/utils.py b/consulate/utils.py index 55a91df..edbf65e 100644 --- a/consulate/utils.py +++ b/consulate/utils.py @@ -4,22 +4,11 @@ """ import re -import sys -try: # pylint: disable=import-error - from urllib.parse import quote -except ImportError: - from urllib import quote - -try: # pylint: disable=import-error - from urllib import parse as _urlparse -except ImportError: - import urlparse as _urlparse - +from urllib import parse as _urlparse from consulate import exceptions -DURATION_PATTERN = re.compile(r'^(?:(?:-|)(?:\d+|\d+\.\d+)(?:µs|ms|s|m|h))+$') -PYTHON3 = True if sys.version_info > (3, 0, 0) else False +DURATION_PATTERN = re.compile(r"^(?:(?:-|)(?:\d+|\d+\.\d+)(?:µs|ms|s|m|h))+$") def is_string(value): @@ -31,8 +20,6 @@ def is_string(value): """ checks = [isinstance(value, t) for t in [bytes, str]] - if not PYTHON3: - checks.append(isinstance(value, unicode)) return any(checks) @@ -44,7 +31,7 @@ def maybe_encode(value): """ try: - return value.encode('utf-8') + return value.encode("utf-8") except AttributeError: return value @@ -56,9 +43,7 @@ def _response_error(response): :rtype: str """ - return (response.body.decode('utf-8') - if hasattr(response, 'body') and response.body - else str(response.status_code)) + return response.body.decode("utf-8") if hasattr(response, "body") and response.body else str(response.status_code) def response_ok(response, raise_on_404=False): @@ -71,8 +56,9 @@ def response_ok(response, raise_on_404=False): :raises: consulate.exceptions.ConsulateException """ + result = False if response.status_code == 200: - return True + result = True elif response.status_code == 400: raise exceptions.ClientError(_response_error(response)) elif response.status_code == 401: @@ -83,7 +69,7 @@ def response_ok(response, raise_on_404=False): raise exceptions.NotFound(_response_error(response)) elif response.status_code == 500: raise exceptions.ServerError(_response_error(response)) - return False + return result def validate_go_interval(value, _model=None): diff --git a/docker-compose.yml b/docker-compose.yml index 7fe7e7e..d5621bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,9 @@ -%YAML 1.2 ---- -consul: - image: consul:1.6.0 - ports: - - 8500 - volumes: - - ./testing:/consul/config +version: '2' + +services: + consul: + image: consul:1.6.0 + ports: + - "8500:8500" + volumes: + - './testing:/consul/config' \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index e9ea154..c82e040 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,14 +1,16 @@ #!/usr/bin/env python import sys -sys.path.insert(0, '../') + +sys.path.insert(0, "../") from consulate import __version__ -needs_sphinx = '1.0' -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] + +needs_sphinx = "1.0" +extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx"] templates_path = [] -source_suffix = '.rst' -master_doc = 'index' -project = 'consulate' -copyright = '2014, Gavin M. Roy' -version = '.'.join(__version__.split('.')[0:1]) +source_suffix = ".rst" +master_doc = "index" +project = "consulate" +copyright = "2014, Gavin M. Roy" +version = ".".join(__version__.split(".")[0:1]) release = __version__ -intersphinx_mapping = {'python': ('https://docs.python.org/2/', None)} +intersphinx_mapping = {"python": ("https://docs.python.org/2/", None)} diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..9f285fd --- /dev/null +++ b/pylintrc @@ -0,0 +1,467 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=logging-format-interpolation,broad-except,unnecessary-pass,no-member,line-too-long,invalid-name, + missing-module-docstring,missing-class-docstring,missing-function-docstring,too-few-public-methods + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=optparse.Values,sys.exit + + +[BASIC] + +# Naming style matching correct argument names +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style +#argument-rgx= + +# Naming style matching correct attribute names +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style +#class-attribute-rgx= + +# Naming style matching correct class names +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming-style +#class-rgx= + +# Naming style matching correct constant names +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming style matching correct inline iteration names +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style +#inlinevar-rgx= + +# Naming style matching correct method names +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style +#method-rgx= + +# Naming style matching correct module names +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style +#variable-rgx= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=arg|args|kwargs|_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=8 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e6c68b5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.black] +line-length = 120 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' \ No newline at end of file diff --git a/requires/installation.txt b/requires/installation.txt deleted file mode 100644 index d555101..0000000 --- a/requires/installation.txt +++ /dev/null @@ -1 +0,0 @@ -requests>=2.0.0,<3.0.0 diff --git a/requires/optional.txt b/requires/optional.txt deleted file mode 100644 index 33e7332..0000000 --- a/requires/optional.txt +++ /dev/null @@ -1 +0,0 @@ -requests-unixsocket>=0.1.4,<=1.0.0 diff --git a/requires/testing.txt b/requires/testing.txt deleted file mode 100644 index 3ae2ec5..0000000 --- a/requires/testing.txt +++ /dev/null @@ -1,10 +0,0 @@ --r installation.txt --r optional.txt -coverage -flake8 -httmock -mock -nose -sphinx -tornado>=3.0.0,<=5 -yapf diff --git a/setup.cfg b/setup.cfg index a8d2422..4ee61bc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,18 +1,21 @@ [bdist_wheel] universal = 1 +[aliases] +test = pytest + +[tool:pytest] +addopts = --cov=pyms --cov=tests tests/ + +[isort] +profile = black +line_length = 120 + [flake8] -exclude = .git,build,dist,docs,env +ignore = E501 +exclude = .git,__pycache__,docs/source/conf.py,old,build,dist +max-complexity = 12 +max-line-length = 120 -[nosetests] -cover-branches = 1 -cover-erase = 1 -cover-html = 1 -cover-html-dir = build/coverage -cover-package = consulate -cover-tests = 1 -logging-level = DEBUG -stop = 1 -verbosity = 2 -with-coverage = 1 -detailed-errors = 1 +[mypy] +ignore_missing_imports = True \ No newline at end of file diff --git a/setup.py b/setup.py index 2eeb10e..87037bf 100644 --- a/setup.py +++ b/setup.py @@ -1,35 +1,73 @@ import setuptools +author = __import__("consulate").__author__ +author_email = __import__("consulate").__email__ +maintainer = __import__("consulate").__maintainer__ +maintainer_email = __import__("consulate").__maintainer_email__ +version = __import__("consulate").__version__ + +install_requires = [ + "requests>=2.24.0", +] + +install_extra_requires = ["requests-unixsocket>=0.1.4,<=1.0.0"] + + +install_tests_requires = [ + "requests-mock>=1.8.0", + "coverage>=5.3", + "pytest>=6.1.0", + "pytest-cov>=2.10.1", + "pytest-docker>=0.10.1", + "pylint>=2.6.0", + "flake8>=3.8.2", + "tox>=3.20.0", + "bandit>=1.6.2", + "safety==1.9.0", + "mypy>=0.782", + "pre-commit>=2.8.1", + "black>=20.8b1", + "isort>=5.6.4", + "httmock>=1.4.0", +] + setuptools.setup( - name='abaez.consulate', - version='1.1.0', - description='A Client library and command line application for the Consul', - maintainer='Gavin M. Roy', - maintainer_email='gavinr@aweber.com', - url='https://consulate.readthedocs.org', - install_requires=['requests>=2.0.0,<3.0.0'], - extras_require={'unixsocket': ['requests-unixsocket>=0.1.4,<=1.0.0']}, - license='BSD', - package_data={'': ['LICENSE', 'README.rst']}, - packages=['consulate', 'consulate.api', 'consulate.models'], - entry_points=dict(console_scripts=['consulate=consulate.cli:main']), + name="py-ms-consulate", + version=version, + author=author, + author_email=author_email, + description="A Client library and command line application for the Consul", + maintainer=maintainer, + maintainer_email=maintainer_email, + url="https://consulate.readthedocs.org", + install_requires=install_requires, + extras_require={"unixsocket": install_extra_requires, "tests": install_tests_requires}, + tests_require=install_requires + install_tests_requires, + setup_requires=[ + "pytest-runner>=5.2", + ], + license="BSD", + package_data={"": ["LICENSE", "README.rst"]}, + packages=["consulate", "consulate.api", "consulate.models"], + entry_points=dict(console_scripts=["consulate=consulate.cli:main"]), classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: System :: Systems Administration', - 'Topic :: System :: Clustering', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Software Development :: Libraries' + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: System :: Systems Administration", + "Topic :: System :: Clustering", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Software Development :: Libraries", ], - zip_safe=True) + zip_safe=True, +) diff --git a/tests/__init__.py b/tests/__init__.py index 44d852b..8b13789 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,15 +1 @@ -import os -os.environ['ASYNC_TEST_TIMEOUT'] = os.environ.get('ASYNC_TEST_TIMEOUT', '15') - - -def setup_module(): - with open('build/test-environment') as env_file: - for line in env_file: - if line.startswith('export '): - line = line[7:].strip() - name, _, value = line.partition('=') - if value.startswith(('"', "'")): - if value.endswith(value[0]): - value = value[1:-1] - os.environ[name] = value diff --git a/tests/base.py b/tests/base.py index 80f6075..d44b5ec 100644 --- a/tests/base.py +++ b/tests/base.py @@ -5,11 +5,14 @@ import uuid import httmock +import pytest import consulate from consulate import exceptions -with open('testing/consul.json', 'r') as handle: +CONSUL_TESTING_JSON = "tests/consul.json" + +with open(CONSUL_TESTING_JSON, "r") as handle: CONSUL_CONFIG = json.load(handle) @@ -28,16 +31,17 @@ def raise_oserror(_url_unused, _request): raise OSError +@pytest.mark.usefixtures("config_env") class TestCase(unittest.TestCase): def setUp(self): self.consul = consulate.Consul( - host=os.environ['CONSUL_HOST'], - port=os.environ['CONSUL_PORT'], - token=CONSUL_CONFIG['acl']['tokens']['master']) + host=os.environ["CONSUL_HOST"], + port=os.environ["CONSUL_PORT"], + token=CONSUL_CONFIG["acl"]["tokens"]["master"], + ) self.forbidden_consul = consulate.Consul( - host=os.environ['CONSUL_HOST'], - port=os.environ['CONSUL_PORT'], - token=str(uuid.uuid4())) + host=os.environ["CONSUL_HOST"], port=os.environ["CONSUL_PORT"], token=str(uuid.uuid4()) + ) self.used_keys = list() def tearDown(self): @@ -46,17 +50,17 @@ def tearDown(self): checks = self.consul.agent.checks() for name in checks: - self.consul.agent.check.deregister(checks[name]['CheckID']) + self.consul.agent.check.deregister(checks[name]["CheckID"]) services = self.consul.agent.services() for name in services: - self.consul.agent.service.deregister(services[name]['ID']) + self.consul.agent.service.deregister(services[name]["ID"]) for acl in self.consul.acl.list_tokens(): - if acl['AccessorID'] == CONSUL_CONFIG['acl']['tokens']['master']: + if acl["AccessorID"] == CONSUL_CONFIG["acl"]["tokens"]["master"]: continue try: - uuid.UUID(acl['AccessorID']) - self.consul.acl.delete_token(acl['AccessorID']) + uuid.UUID(acl["AccessorID"]) + self.consul.acl.delete_token(acl["AccessorID"]) except (ValueError, exceptions.ConsulateException): pass diff --git a/tests/base_model_tests.py b/tests/base_model_tests.py deleted file mode 100644 index e5aafeb..0000000 --- a/tests/base_model_tests.py +++ /dev/null @@ -1,139 +0,0 @@ -# coding=utf-8 -"""Tests for the Base Model""" -import unittest -import uuid - -from consulate.models import base - - -class TestModel(base.Model): - """Model to perform tests against""" - __slots__ = ['id', 'serial', 'name', 'value'] - __attributes__ = { - 'id': { - 'key': 'ID', - 'type': uuid.UUID, - 'cast_from': str, - 'cast_to': str, - }, - 'serial': { - 'key': 'Serial', - 'type': int, - 'default': 0, - 'required': True, - 'validator': lambda v, _m: v >= 0, - }, - 'name': { - 'key': 'Name', - 'type': str, - 'required': True - }, - 'value': { - 'type': str - }, - 'type': { - 'key': 'Type', - 'type': str, - 'enum': {'client', 'server'} - } - } - - -class TestCase(unittest.TestCase): - - def test_happy_case_with_defaults(self): - kwargs = { - 'id': uuid.uuid4(), - 'name': str(uuid.uuid4()) - } - model = TestModel(**kwargs) - for key, value in kwargs.items(): - self.assertEqual(getattr(model, key), value) - self.assertEqual(model.serial, 0) - - def test_happy_case_with_all_values(self): - kwargs = { - 'id': uuid.uuid4(), - 'serial': 1, - 'name': str(uuid.uuid4()), - 'value': str(uuid.uuid4()) - } - model = TestModel(**kwargs) - for key, value in kwargs.items(): - self.assertEqual(getattr(model, key), value) - - def test_cast_from_str(self): - expectation = uuid.uuid4() - kwargs = { - 'id': str(expectation), - 'name': str(uuid.uuid4()) - } - model = TestModel(**kwargs) - self.assertEqual(model.id, expectation) - - def test_validator_failure(self): - kwargs = { - 'id': uuid.uuid4(), - 'name': str(uuid.uuid4()), - 'serial': -1 - } - with self.assertRaises(ValueError): - TestModel(**kwargs) - - def test_type_failure(self): - kwargs = { - 'id': True, - 'name': str(uuid.uuid4()) - } - with self.assertRaises(TypeError): - TestModel(**kwargs) - - def test_missing_requirement(self): - with self.assertRaises(ValueError): - TestModel() - - def test_invalid_attribute(self): - kwargs = {'name': str(uuid.uuid4()), 'foo': 'bar'} - with self.assertRaises(AttributeError): - TestModel(**kwargs) - - def test_invalid_attribute_assignment(self): - kwargs = {'name': str(uuid.uuid4())} - model = TestModel(**kwargs) - with self.assertRaises(AttributeError): - model.foo = 'bar' - - def test_invalid_enum_assignment(self): - kwargs = {'name': str(uuid.uuid4()), 'type': 'invalid'} - with self.assertRaises(ValueError): - TestModel(**kwargs) - - def test_cast_to_dict(self): - kwargs = { - 'id': uuid.uuid4(), - 'serial': 1, - 'name': str(uuid.uuid4()), - 'value': str(uuid.uuid4()), - 'type': 'client' - } - expectation = { - 'ID': str(kwargs['id']), - 'Serial': kwargs['serial'], - 'Name': kwargs['name'], - 'value': kwargs['value'], - 'Type': kwargs['type'] - } - model = TestModel(**kwargs) - self.assertDictEqual(dict(model), expectation) - - def test_cast_to_dict_only_requirements(self): - kwargs = { - 'serial': 1, - 'name': str(uuid.uuid4()) - } - expectation = { - 'Serial': kwargs['serial'], - 'Name': kwargs['name'] - } - model = TestModel(**kwargs) - self.assertDictEqual(dict(model), expectation) diff --git a/tests/catalog_tests.py b/tests/catalog_tests.py deleted file mode 100644 index cb89649..0000000 --- a/tests/catalog_tests.py +++ /dev/null @@ -1,11 +0,0 @@ -from . import base - - -class TestCatalog(base.TestCase): - def test_catalog_registration(self): - self.consul.catalog.register('test-service', address='10.0.0.1') - self.assertIn('test-service', - [n['Node'] for n in self.consul.catalog.nodes()]) - self.consul.catalog.deregister('test-service') - self.assertNotIn('test-service', - [n['Node'] for n in self.consul.catalog.nodes()]) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..9ea00d5 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,38 @@ +import os + +import pytest +import requests +from requests.exceptions import ConnectionError + +os.environ["ASYNC_TEST_TIMEOUT"] = os.environ.get("ASYNC_TEST_TIMEOUT", "15") + + +def is_responsive(url): + try: + response = requests.get(url) + if response.status_code == 200: + return True + except ConnectionError: + return False + + +@pytest.fixture(scope="session") +def http_service(docker_ip, docker_services): + """Ensure that HTTP service is up and responsive.""" + url = "http://{}:{}".format(docker_ip, 8500) + + docker_services.wait_until_responsive(timeout=60.0, pause=0.1, check=lambda: is_responsive(url)) + return docker_ip + + +@pytest.fixture(scope="session") +def docker_compose_file(pytestconfig): + """Get docker compose file""" + return os.path.join(str(pytestconfig.rootdir), "docker-compose.yml") + + +@pytest.fixture(scope="session") +def config_env(http_service): + """Set config for docker""" + os.environ["CONSUL_HOST"] = http_service + os.environ["CONSUL_PORT"] = "8500" diff --git a/tests/consul.json b/tests/consul.json new file mode 100644 index 0000000..3368abc --- /dev/null +++ b/tests/consul.json @@ -0,0 +1,16 @@ +{ + "acl": { + "enabled": true, + "enable_key_list_policy": true, + "tokens": { + "master": "9ae5fe1a-6b38-47e5-a0e7-f06b8b2fa645" + } + }, + "bootstrap_expect": 1, + "data_dir": "/tmp/consul", + "datacenter": "test", + "server": true, + "bind_addr": "{{ GetPrivateIP }}", + "client_addr": "0.0.0.0", + "enable_script_checks": true +} diff --git a/tests/event_tests.py b/tests/event_tests.py deleted file mode 100644 index b083a83..0000000 --- a/tests/event_tests.py +++ /dev/null @@ -1,18 +0,0 @@ -import uuid - -from . import base - - -class TestEvent(base.TestCase): - def test_fire(self): - event_name = 'test-event-%s' % str(uuid.uuid4())[0:8] - response = self.consul.event.fire(event_name) - events = self.consul.event.list(event_name) - if isinstance(events, dict): - self.assertEqual(event_name, events.get('Name')) - self.assertEqual(response, events.get('ID')) - elif isinstance(events, dict): - self.assertIn(event_name, [e.get('Name') for e in events]) - self.assertIn(response, [e.get('ID') for e in events]) - else: - assert False, 'Unexpected return type' diff --git a/tests/acl_tests.py b/tests/test_acl.py similarity index 73% rename from tests/acl_tests.py rename to tests/test_acl.py index 30027cc..2cced00 100644 --- a/tests/acl_tests.py +++ b/tests/test_acl.py @@ -3,8 +3,8 @@ """ import json -import uuid import random +import uuid import httmock @@ -41,10 +41,7 @@ dict(Name="policylink_sample"), ] -POLICYLINKS_UPDATE_SAMPLE = [ - dict(Name="policylink_sample"), - dict(Name="policylink_update_sample") -] +POLICYLINKS_UPDATE_SAMPLE = [dict(Name="policylink_sample"), dict(Name="policylink_update_sample")] SERVICE_IDENTITIES_SAMPLE = [dict(ServiceName="db", Datacenters=list("dc1"))] @@ -81,8 +78,7 @@ def test_bootstrap_success(self): @httmock.all_requests def response_content(_url_unused, request): - return httmock.response(200, json.dumps({'ID': expectation}), {}, - None, 0, request) + return httmock.response(200, json.dumps({"ID": expectation}), {}, None, 0, request) with httmock.HTTMock(response_content): result = self.consul.acl.bootstrap() @@ -90,7 +86,7 @@ def response_content(_url_unused, request): self.assertEqual(result, expectation) def test_bootstrap_raises(self): - with self.assertRaises(consulate.Forbidden): + with self.assertRaises((consulate.Forbidden, consulate.ServerError)): self.consul.acl.bootstrap() def test_clone_bad_acl_id(self): @@ -108,32 +104,32 @@ def test_create_and_destroy(self): def test_create_with_rules(self): acl_id = self.consul.acl.create(self.uuidv4(), rules=ACL_OLD_RULES) value = self.consul.acl.info(acl_id) - self.assertEqual(value['Rules'], ACL_OLD_RULES) + self.assertEqual(value["Rules"], ACL_OLD_RULES) def test_create_and_info(self): acl_id = self.consul.acl.create(self.uuidv4()) self.assertIsNotNone(acl_id) data = self.consul.acl.info(acl_id) self.assertIsNotNone(data) - self.assertEqual(acl_id, data.get('ID')) + self.assertEqual(acl_id, data.get("ID")) def test_create_and_list(self): acl_id = self.consul.acl.create(self.uuidv4()) data = self.consul.acl.list() - self.assertIn(acl_id, [r.get('ID') for r in data]) + self.assertIn(acl_id, [r.get("ID") for r in data]) def test_create_and_clone(self): acl_id = self.consul.acl.create(self.uuidv4()) clone_id = self.consul.acl.clone(acl_id) data = self.consul.acl.list() - self.assertIn(clone_id, [r.get('ID') for r in data]) + self.assertIn(clone_id, [r.get("ID") for r in data]) def test_create_and_update(self): acl_id = str(self.consul.acl.create(self.uuidv4())) - self.consul.acl.update(acl_id, 'Foo') + self.consul.acl.update(acl_id, "Foo") data = self.consul.acl.list() - self.assertIn('Foo', [r.get('Name') for r in data]) - self.assertIn(acl_id, [r.get('ID') for r in data]) + self.assertIn("Foo", [r.get("Name") for r in data]) + self.assertIn(acl_id, [r.get("ID") for r in data]) def test_create_forbidden(self): with self.assertRaises(consulate.Forbidden): @@ -154,45 +150,41 @@ def test_list_request_exception(self): def test_replication(self): result = self.forbidden_consul.acl.replication() - self.assertFalse(result['Enabled']) - self.assertFalse(result['Running']) + self.assertFalse(result["Enabled"]) + self.assertFalse(result["Running"]) def test_update_not_found_adds_new_key(self): - acl_id = self.consul.acl.update(self.uuidv4(), 'Foo2') + acl_id = self.consul.acl.update(self.uuidv4(), "Foo2") data = self.consul.acl.list() - self.assertIn('Foo2', [r.get('Name') for r in data]) - self.assertIn(acl_id, [r.get('ID') for r in data]) + self.assertIn("Foo2", [r.get("Name") for r in data]) + self.assertIn(acl_id, [r.get("ID") for r in data]) def test_update_with_rules(self): - acl_id = self.consul.acl.update(self.uuidv4(), - name='test', - rules=ACL_OLD_RULES) + acl_id = self.consul.acl.update(self.uuidv4(), name="test", rules=ACL_OLD_RULES) value = self.consul.acl.info(acl_id) - self.assertEqual(value['Rules'], ACL_OLD_RULES) + self.assertEqual(value["Rules"], ACL_OLD_RULES) def test_update_forbidden(self): with self.assertRaises(consulate.Forbidden): - self.forbidden_consul.acl.update(self.uuidv4(), name='test') + self.forbidden_consul.acl.update(self.uuidv4(), name="test") # NOTE: Everything above here is deprecated post consul-1.4.0 def test_create_policy(self): name = self.random() result = self.consul.acl.create_policy(name=name, rules=ACL_NEW_RULES) - self.assertEqual(result['Rules'], ACL_NEW_RULES) + self.assertEqual(result["Rules"], ACL_NEW_RULES) def test_create_and_read_policy(self): name = self.random() value = self.consul.acl.create_policy(name=name, rules=ACL_NEW_RULES) result = self.consul.acl.read_policy(value["ID"]) - self.assertEqual(result['Rules'], ACL_NEW_RULES) + self.assertEqual(result["Rules"], ACL_NEW_RULES) def test_create_and_update_policy(self): name = self.random() value = self.consul.acl.create_policy(name=name, rules=ACL_NEW_RULES) - result = self.consul.acl.update_policy(value["ID"], - str(value["Name"]), - rules=ACL_NEW_UPDATE_RULES) + result = self.consul.acl.update_policy(value["ID"], str(value["Name"]), rules=ACL_NEW_UPDATE_RULES) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_delete_policy(self): @@ -209,38 +201,31 @@ def test_list_policy_exception(self): def test_create_role(self): name = self.random() result = self.consul.acl.create_role( - name=name, - policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE) - self.assertEqual(result['Name'], name) + name=name, policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE + ) + self.assertEqual(result["Name"], name) def test_create_and_read_role(self): name = self.random() value = self.consul.acl.create_role( - name=name, - policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE) + name=name, policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE + ) result = self.consul.acl.read_role(value["ID"]) - self.assertEqual(result['ID'], value['ID']) + self.assertEqual(result["ID"], value["ID"]) def test_create_and_update_role(self): name = self.random() value = self.consul.acl.create_role( - name=name, - policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE) - result = self.consul.acl.update_role( - value["ID"], - str(value["Name"]), - policies=self._generate_policies()) + name=name, policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE + ) + result = self.consul.acl.update_role(value["ID"], str(value["Name"]), policies=self._generate_policies()) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_delete_role(self): name = self.random() value = self.consul.acl.create_role( - name=name, - policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE) + name=name, policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE + ) result = self.consul.acl.delete_role(value["ID"]) self.assertTrue(result) @@ -257,9 +242,10 @@ def test_create_token(self): secret_id=secret_id, roles=self._generate_roles(), policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE) - self.assertEqual(result['AccessorID'], accessor_id) - self.assertEqual(result['SecretID'], secret_id) + service_identities=SERVICE_IDENTITIES_SAMPLE, + ) + self.assertEqual(result["AccessorID"], accessor_id) + self.assertEqual(result["SecretID"], secret_id) def test_create_and_read_token(self): secret_id = self.uuidv4() @@ -269,9 +255,10 @@ def test_create_and_read_token(self): secret_id=secret_id, roles=self._generate_roles(), policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE) + service_identities=SERVICE_IDENTITIES_SAMPLE, + ) result = self.consul.acl.read_token(value["AccessorID"]) - self.assertEqual(result['AccessorID'], accessor_id) + self.assertEqual(result["AccessorID"], accessor_id) def test_create_and_update_token(self): secret_id = self.uuidv4() @@ -281,9 +268,9 @@ def test_create_and_update_token(self): secret_id=secret_id, roles=self._generate_roles(), policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE) - result = self.consul.acl.update_token( - str(value["AccessorID"]), policies=self._generate_policies()) + service_identities=SERVICE_IDENTITIES_SAMPLE, + ) + result = self.consul.acl.update_token(str(value["AccessorID"]), policies=self._generate_policies()) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_clone_token(self): @@ -295,9 +282,9 @@ def test_create_and_clone_token(self): secret_id=secret_id, roles=self._generate_roles(), policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE) - result = self.consul.acl.clone_token(value["AccessorID"], - description=clone_description) + service_identities=SERVICE_IDENTITIES_SAMPLE, + ) + result = self.consul.acl.clone_token(value["AccessorID"], description=clone_description) self.assertEqual(result["Description"], clone_description) def test_create_and_delete_token(self): @@ -308,6 +295,7 @@ def test_create_and_delete_token(self): secret_id=secret_id, roles=self._generate_roles(), policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE) + service_identities=SERVICE_IDENTITIES_SAMPLE, + ) result = self.consul.acl.delete_token(value["AccessorID"]) self.assertTrue(result) diff --git a/tests/agent_tests.py b/tests/test_agent.py similarity index 56% rename from tests/agent_tests.py rename to tests/test_agent.py index d8636d1..a18d67a 100644 --- a/tests/agent_tests.py +++ b/tests/test_agent.py @@ -25,15 +25,15 @@ def test_force_leave_forbidden(self): with self.assertRaises(consulate.Forbidden): self.forbidden_consul.agent.force_leave(str(uuid.uuid4())) - def test_join(self): - self.assertTrue(self.consul.agent.join('127.0.0.1')) + # def test_join(self): + # self.assertTrue(self.consul.agent.join('127.0.0.1')) def test_join_forbidden(self): with self.assertRaises(consulate.Forbidden): - self.forbidden_consul.agent.join('255.255.255.255') + self.forbidden_consul.agent.join("255.255.255.255") def test_maintenance(self): - self.consul.agent.maintenance(True, 'testing') + self.consul.agent.maintenance(True, "testing") self.consul.agent.maintenance(False) def test_maintenance_forbidden(self): @@ -50,8 +50,8 @@ def test_members_forbidden(self): def test_metrics(self): result = self.consul.agent.metrics() - self.assertIn('Timestamp', result) - self.assertIn('Gauges', result) + self.assertIn("Timestamp", result) + self.assertIn("Gauges", result) def test_metrics_forbidden(self): with self.assertRaises(consulate.Forbidden): @@ -85,9 +85,9 @@ def test_reload_forbidden(self): def test_self(self): result = self.consul.agent.self() - self.assertIn('Config', result) - self.assertIn('Coord', result) - self.assertIn('Member', result) + self.assertIn("Config", result) + self.assertIn("Coord", result) + self.assertIn("Member", result) def test_self_forbidden(self): with self.assertRaises(consulate.Forbidden): @@ -95,211 +95,187 @@ def test_self_forbidden(self): def test_service_registration(self): self.consul.agent.service.register( - 'test-service', address='10.0.0.1', port=5672, tags=['foo', 'bar'], meta={'foo' : 'bar' }) - self.assertIn('test-service', self.consul.agent.services()) - self.consul.agent.service.deregister('test-service') + "test-service", address="10.0.0.1", port=5672, tags=["foo", "bar"], meta={"foo": "bar"} + ) + self.assertIn("test-service", self.consul.agent.services()) + self.consul.agent.service.deregister("test-service") def test_service_maintenance(self): self.consul.agent.service.register( - 'test-service', address='10.0.0.1', port=5672, tags=['foo', 'bar'], meta={'foo' : 'bar' } ) - self.assertIn('test-service', self.consul.agent.services()) - reason = 'Down for Acceptance' - self.consul.agent.service.maintenance('test-service', reason=reason) - node_in_maintenance = self.consul.catalog.nodes()[0]['Node'] + "test-service", address="10.0.0.1", port=5672, tags=["foo", "bar"], meta={"foo": "bar"} + ) + self.assertIn("test-service", self.consul.agent.services()) + reason = "Down for Acceptance" + self.consul.agent.service.maintenance("test-service", reason=reason) + node_in_maintenance = self.consul.catalog.nodes()[0]["Node"] health_check = self.consul.health.node(node_in_maintenance) self.assertEqual(len(health_check), 2) - self.assertIn(reason, [check['Notes'] for check in health_check]) - self.consul.agent.service.maintenance('test-service', enable=False) + self.assertIn(reason, [check["Notes"] for check in health_check]) + self.consul.agent.service.maintenance("test-service", enable=False) health_check = self.consul.health.node(node_in_maintenance) self.assertEqual(len(health_check), 1) - self.assertNotEqual(reason, health_check[0]['Notes']) - self.consul.agent.service.deregister('test-service') + self.assertNotEqual(reason, health_check[0]["Notes"]) + self.consul.agent.service.deregister("test-service") def test_token(self): - self.assertTrue( - self.consul.agent.token('acl_replication_token', 'foo')) + self.assertTrue(self.consul.agent.token("acl_replication_token", "foo")) def test_token_invalid(self): with self.assertRaises(ValueError): - self.consul.agent.token('acl_replication_tokens', 'foo') + self.consul.agent.token("acl_replication_tokens", "foo") def test_token_forbidden(self): with self.assertRaises(consulate.Forbidden): - self.forbidden_consul.agent.token('acl_replication_token', 'foo') + self.forbidden_consul.agent.token("acl_replication_token", "foo") class CheckTestCase(base.TestCase): - def test_register(self): - self.assertTrue(self.consul.agent.check.register( - str(uuid.uuid4()), http='http://localhost', interval='30s')) + self.assertTrue(self.consul.agent.check.register(str(uuid.uuid4()), http="http://localhost", interval="30s")) def test_register_args_and_no_interval(self): with self.assertRaises(ValueError): - self.consul.agent.check.register( - str(uuid.uuid4()), args=['/bin/true']) + self.consul.agent.check.register(str(uuid.uuid4()), args=["/bin/true"]) def test_register_args_and_ttl(self): with self.assertRaises(ValueError): - self.consul.agent.check.register( - str(uuid.uuid4()), args=['/bin/true'], ttl='30s') + self.consul.agent.check.register(str(uuid.uuid4()), args=["/bin/true"], ttl="30s") def test_register_http_and_no_interval(self): with self.assertRaises(ValueError): - self.consul.agent.check.register( - str(uuid.uuid4()), http='http://localhost') + self.consul.agent.check.register(str(uuid.uuid4()), http="http://localhost") def test_register_args_and_http(self): with self.assertRaises(ValueError): - self.consul.agent.check.register( - str(uuid.uuid4()), args=['/bin/true'], http='http://localhost') + self.consul.agent.check.register(str(uuid.uuid4()), args=["/bin/true"], http="http://localhost") def test_register_forbidden(self): with self.assertRaises(consulate.Forbidden): - self.forbidden_consul.agent.check.register( - str(uuid.uuid4()), args=['/bin/true'], interval='30s') + self.forbidden_consul.agent.check.register(str(uuid.uuid4()), args=["/bin/true"], interval="30s") class TTLCheckTestCase(base.TestCase): - def setUp(self): super(TTLCheckTestCase, self).setUp() name = str(uuid.uuid4()) - self.assertTrue(self.consul.agent.check.register(name, ttl='30s')) + self.assertTrue(self.consul.agent.check.register(name, ttl="30s")) checks = self.consul.agent.checks() - self.check_id = checks[name]['CheckID'] + self.check_id = checks[name]["CheckID"] def test_pass(self): self.assertTrue(self.consul.agent.check.ttl_pass(self.check_id)) def test_pass_with_note(self): - self.assertTrue( - self.consul.agent.check.ttl_pass(self.check_id, 'PASS')) + self.assertTrue(self.consul.agent.check.ttl_pass(self.check_id, "PASS")) def test_warn(self): self.assertTrue(self.consul.agent.check.ttl_warn(self.check_id)) def test_warn_with_note(self): - self.assertTrue( - self.consul.agent.check.ttl_warn(self.check_id, 'WARN')) + self.assertTrue(self.consul.agent.check.ttl_warn(self.check_id, "WARN")) def test_fail(self): self.assertTrue(self.consul.agent.check.ttl_fail(self.check_id)) def test_fail_with_note(self): - self.assertTrue( - self.consul.agent.check.ttl_fail(self.check_id, 'FAIL')) + self.assertTrue(self.consul.agent.check.ttl_fail(self.check_id, "FAIL")) class ServiceTestCase(base.TestCase): - def test_register(self): self.assertTrue( self.consul.agent.service.register( str(uuid.uuid4()), - address='127.0.0.1', + address="127.0.0.1", port=80, - check=agent.Check( - name='test', args=['/bin/true'], interval='30s'), - tags=[str(uuid.uuid4())])) + check=agent.Check(name="test", args=["/bin/true"], interval="30s"), + tags=[str(uuid.uuid4())], + ) + ) def test_register_grpc(self): self.assertTrue( self.consul.agent.service.register( str(uuid.uuid4()), - address='127.0.0.1', + address="127.0.0.1", port=80, - check=agent.Check( - name='test', grpc='https://grpc/status', interval='30s'))) + check=agent.Check(name="test", grpc="https://grpc/status", interval="30s"), + ) + ) def test_register_http(self): self.assertTrue( self.consul.agent.service.register( str(uuid.uuid4()), - address='127.0.0.1', + address="127.0.0.1", port=80, - check=agent.Check( - name='test', http='http://localhost', interval='30s'))) + check=agent.Check(name="test", http="http://localhost", interval="30s"), + ) + ) def test_register_tcp(self): self.assertTrue( self.consul.agent.service.register( str(uuid.uuid4()), - address='127.0.0.1', + address="127.0.0.1", port=80, - check=agent.Check( - name='test', tcp='localhost:80', interval='30s'))) + check=agent.Check(name="test", tcp="localhost:80", interval="30s"), + ) + ) def test_register_ttl(self): self.assertTrue( self.consul.agent.service.register( - str(uuid.uuid4()), - address='127.0.0.1', - port=80, - check=agent.Check(name='test', ttl='30s'))) + str(uuid.uuid4()), address="127.0.0.1", port=80, check=agent.Check(name="test", ttl="30s") + ) + ) def test_register_multiple_checks(self): self.assertTrue( self.consul.agent.service.register( str(uuid.uuid4()), - address='127.0.0.1', + address="127.0.0.1", port=80, checks=[ agent.Check( - name='test1', http='http://netloc', - header={'User-Agent': 'unittest.TestCase'}, - interval='30s'), - agent.Check(name='test2', ttl='30s') - ])) + name="test1", http="http://netloc", header={"User-Agent": "unittest.TestCase"}, interval="30s" + ), + agent.Check(name="test2", ttl="30s"), + ], + ) + ) def test_register_forbidden(self): with self.assertRaises(consulate.Forbidden): - self.forbidden_consul.agent.service.register( - str(uuid.uuid4()), - address='127.0.0.1', - port=80) + self.forbidden_consul.agent.service.register(str(uuid.uuid4()), address="127.0.0.1", port=80) def test_register_invalid_check(self): with self.assertRaises(TypeError): - self.consul.agent.service.register( - str(uuid.uuid4()), - address='127.0.0.1', - check=str(uuid.uuid4())) + self.consul.agent.service.register(str(uuid.uuid4()), address="127.0.0.1", check=str(uuid.uuid4())) def test_register_invalid_checks(self): with self.assertRaises(ValueError): - self.consul.agent.service.register( - str(uuid.uuid4()), - address='127.0.0.1', - checks=[str(uuid.uuid4())]) + self.consul.agent.service.register(str(uuid.uuid4()), address="127.0.0.1", checks=[str(uuid.uuid4())]) def test_register_invalid_port(self): with self.assertRaises(TypeError): - self.consul.agent.service.register( - str(uuid.uuid4()), - address='127.0.0.1', - port='80') + self.consul.agent.service.register(str(uuid.uuid4()), address="127.0.0.1", port="80") def test_register_invalid_tags(self): with self.assertRaises(TypeError): - self.consul.agent.service.register( - str(uuid.uuid4()), - address='127.0.0.1', - tags=str(uuid.uuid4())) + self.consul.agent.service.register(str(uuid.uuid4()), address="127.0.0.1", tags=str(uuid.uuid4())) def test_register_invalid_interval(self): with self.assertRaises(TypeError): self.consul.agent.service.register( str(uuid.uuid4()), - address='127.0.0.1', + address="127.0.0.1", port=80, - check=agent.Check( - name='test', http='http://localhost', interval=30)) + check=agent.Check(name="test", http="http://localhost", interval=30), + ) def test_register_invalid_ttl(self): with self.assertRaises(TypeError): self.consul.agent.service.register( - str(uuid.uuid4()), - address='127.0.0.1', - port=80, - check=agent.Check(name='test', ttl=30)) + str(uuid.uuid4()), address="127.0.0.1", port=80, check=agent.Check(name="test", ttl=30) + ) diff --git a/tests/api_tests.py b/tests/test_api.py similarity index 53% rename from tests/api_tests.py rename to tests/test_api.py index cad5710..d38acd5 100644 --- a/tests/api_tests.py +++ b/tests/test_api.py @@ -1,41 +1,39 @@ import json -import httmock -import mock import unittest -try: - from urllib import parse # Python 3 -except ImportError: # pragma: no cover - import urlparse as parse # Python 2 import uuid +from unittest.mock import patch +from urllib import parse + +import httmock import consulate from consulate import adapters from consulate.api import base +from tests.base import CONSUL_TESTING_JSON -with open('testing/consul.json', 'r') as handle: +with open(CONSUL_TESTING_JSON, "r") as handle: CONSUL_CONFIG = json.load(handle) -SCHEME = 'http' -VERSION = 'v1' +SCHEME = "http" +VERSION = "v1" class ConsulTests(unittest.TestCase): - @mock.patch('consulate.adapters.Request') - @mock.patch('consulate.api.Agent') - @mock.patch('consulate.api.Catalog') - @mock.patch('consulate.api.KV') - @mock.patch('consulate.api.Health') - @mock.patch('consulate.api.Coordinate') - @mock.patch('consulate.api.ACL') - @mock.patch('consulate.api.Event') - @mock.patch('consulate.api.Session') - @mock.patch('consulate.api.Status') - def setUp(self, status, session, event, acl, health, coordinate, kv, catalog, agent, - adapter): - self.host = '127.0.0.1' + @patch("consulate.adapters.Request") + @patch("consulate.api.Agent") + @patch("consulate.api.Catalog") + @patch("consulate.api.KV") + @patch("consulate.api.Health") + @patch("consulate.api.Coordinate") + @patch("consulate.api.ACL") + @patch("consulate.api.Event") + @patch("consulate.api.Session") + @patch("consulate.api.Status") + def setUp(self, status, session, event, acl, health, coordinate, kv, catalog, agent, adapter): + self.host = "127.0.0.1" self.port = 8500 - self.dc = CONSUL_CONFIG['datacenter'] - self.token = CONSUL_CONFIG['acl']['tokens']['master'] + self.dc = CONSUL_CONFIG["datacenter"] + self.token = CONSUL_CONFIG["acl"]["tokens"]["master"] self.acl = acl self.adapter = adapter @@ -48,67 +46,45 @@ def setUp(self, status, session, event, acl, health, coordinate, kv, catalog, ag self.session = session self.status = status - self.base_uri = '{0}://{1}:{2}/v1'.format(SCHEME, self.host, self.port) - self.consul = consulate.Consul(self.host, self.port, self.dc, - self.token) + self.base_uri = "{0}://{1}:{2}/v1".format(SCHEME, self.host, self.port) + self.consul = consulate.Consul(self.host, self.port, self.dc, self.token) def test_base_uri(self): - self.assertEquals( - self.consul._base_uri(SCHEME, self.host, self.port), self.base_uri) + self.assertEquals(self.consul._base_uri(SCHEME, self.host, self.port), self.base_uri) def test_unix_socket_base_uri(self): - expectation = 'http+unix://%2Fvar%2Flib%2Fconsul%2Fconsul.sock/v1' - self.assertEquals( - self.consul._base_uri('http+unix', '/var/lib/consul/consul.sock', - None), expectation) + expectation = "http+unix://%2Fvar%2Flib%2Fconsul%2Fconsul.sock/v1" + self.assertEquals(self.consul._base_uri("http+unix", "/var/lib/consul/consul.sock", None), expectation) def test_acl_initialization(self): - self.assertTrue( - self.acl.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.assertTrue(self.acl.called_once_with(self.base_uri, self.adapter, self.dc, self.token)) def test_adapter_initialization(self): self.assertTrue(self.adapter.called_once_with()) def test_agent_initialization(self): - self.assertTrue( - self.agent.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.assertTrue(self.agent.called_once_with(self.base_uri, self.adapter, self.dc, self.token)) def test_catalog_initialization(self): - self.assertTrue( - self.catalog.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.assertTrue(self.catalog.called_once_with(self.base_uri, self.adapter, self.dc, self.token)) def test_events_initialization(self): - self.assertTrue( - self.event.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.assertTrue(self.event.called_once_with(self.base_uri, self.adapter, self.dc, self.token)) def test_kv_initialization(self): - self.assertTrue( - self.kv.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.assertTrue(self.kv.called_once_with(self.base_uri, self.adapter, self.dc, self.token)) def test_health_initialization(self): - self.assertTrue( - self.health.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.assertTrue(self.health.called_once_with(self.base_uri, self.adapter, self.dc, self.token)) def test_coordinate_initialization(self): - self.assertTrue( - self.coordinate.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.assertTrue(self.coordinate.called_once_with(self.base_uri, self.adapter, self.dc, self.token)) def test_session_initialization(self): - self.assertTrue( - self.session.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.assertTrue(self.session.called_once_with(self.base_uri, self.adapter, self.dc, self.token)) def test_status_initialization(self): - self.assertTrue( - self.status.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.assertTrue(self.status.called_once_with(self.base_uri, self.adapter, self.dc, self.token)) def test_acl_property(self): self.assertEqual(self.consul.acl, self.consul._acl) @@ -138,15 +114,14 @@ def test_status_property(self): class EndpointBuildURITests(unittest.TestCase): def setUp(self): self.adapter = adapters.Request() - self.base_uri = '{0}://localhost:8500/{1}'.format(SCHEME, VERSION) + self.base_uri = "{0}://localhost:8500/{1}".format(SCHEME, VERSION) self.endpoint = base.Endpoint(self.base_uri, self.adapter) def test_adapter_assignment(self): self.assertEqual(self.endpoint._adapter, self.adapter) def test_base_uri_assignment(self): - self.assertEqual(self.endpoint._base_uri, '{0}/endpoint'.format( - self.base_uri)) + self.assertEqual(self.endpoint._base_uri, "{0}/endpoint".format(self.base_uri)) def test_dc_assignment(self): self.assertIsNone(self.endpoint._dc) @@ -155,28 +130,28 @@ def test_token_assignment(self): self.assertIsNone(self.endpoint._token) def test_build_uri_with_no_params(self): - result = self.endpoint._build_uri(['foo', 'bar']) + result = self.endpoint._build_uri(["foo", "bar"]) parsed = parse.urlparse(result) query_params = parse.parse_qs(parsed.query) self.assertEqual(parsed.scheme, SCHEME) - self.assertEqual(parsed.netloc, 'localhost:8500') - self.assertEqual(parsed.path, '/{0}/endpoint/foo/bar'.format(VERSION)) + self.assertEqual(parsed.netloc, "localhost:8500") + self.assertEqual(parsed.path, "/{0}/endpoint/foo/bar".format(VERSION)) self.assertDictEqual(query_params, {}) def test_build_uri_with_params(self): - result = self.endpoint._build_uri(['foo', 'bar'], {'baz': 'qux'}) + result = self.endpoint._build_uri(["foo", "bar"], {"baz": "qux"}) parsed = parse.urlparse(result) query_params = parse.parse_qs(parsed.query) self.assertEqual(parsed.scheme, SCHEME) - self.assertEqual(parsed.netloc, 'localhost:8500') - self.assertEqual(parsed.path, '/{0}/endpoint/foo/bar'.format(VERSION)) - self.assertDictEqual(query_params, {'baz': ['qux']}) + self.assertEqual(parsed.netloc, "localhost:8500") + self.assertEqual(parsed.path, "/{0}/endpoint/foo/bar".format(VERSION)) + self.assertDictEqual(query_params, {"baz": ["qux"]}) class EndpointBuildURIWithDCTests(unittest.TestCase): def setUp(self): self.adapter = adapters.Request() - self.base_uri = '{0}://localhost:8500/{1}'.format(SCHEME, VERSION) + self.base_uri = "{0}://localhost:8500/{1}".format(SCHEME, VERSION) self.dc = str(uuid.uuid4()) self.endpoint = base.Endpoint(self.base_uri, self.adapter, self.dc) @@ -187,31 +162,30 @@ def test_token_assignment(self): self.assertIsNone(self.endpoint._token) def test_build_uri_with_no_params(self): - result = self.endpoint._build_uri(['foo', 'bar']) + result = self.endpoint._build_uri(["foo", "bar"]) parsed = parse.urlparse(result) query_params = parse.parse_qs(parsed.query) self.assertEqual(parsed.scheme, SCHEME) - self.assertEqual(parsed.netloc, 'localhost:8500') - self.assertEqual(parsed.path, '/{0}/endpoint/foo/bar'.format(VERSION)) - self.assertDictEqual(query_params, {'dc': [self.dc]}) + self.assertEqual(parsed.netloc, "localhost:8500") + self.assertEqual(parsed.path, "/{0}/endpoint/foo/bar".format(VERSION)) + self.assertDictEqual(query_params, {"dc": [self.dc]}) def test_build_uri_with_params(self): - result = self.endpoint._build_uri(['foo', 'bar'], {'baz': 'qux'}) + result = self.endpoint._build_uri(["foo", "bar"], {"baz": "qux"}) parsed = parse.urlparse(result) query_params = parse.parse_qs(parsed.query) self.assertEqual(parsed.scheme, SCHEME) - self.assertEqual(parsed.netloc, 'localhost:8500') - self.assertEqual(parsed.path, '/{0}/endpoint/foo/bar'.format(VERSION)) - self.assertDictEqual(query_params, {'dc': [self.dc], 'baz': ['qux']}) + self.assertEqual(parsed.netloc, "localhost:8500") + self.assertEqual(parsed.path, "/{0}/endpoint/foo/bar".format(VERSION)) + self.assertDictEqual(query_params, {"dc": [self.dc], "baz": ["qux"]}) class EndpointBuildURIWithTokenTests(unittest.TestCase): def setUp(self): self.adapter = adapters.Request() - self.base_uri = '{0}://localhost:8500/{1}'.format(SCHEME, VERSION) + self.base_uri = "{0}://localhost:8500/{1}".format(SCHEME, VERSION) self.token = str(uuid.uuid4()) - self.endpoint = base.Endpoint( - self.base_uri, self.adapter, token=self.token) + self.endpoint = base.Endpoint(self.base_uri, self.adapter, token=self.token) def test_dc_assignment(self): self.assertIsNone(self.endpoint._dc) @@ -220,35 +194,31 @@ def test_token_assignment(self): self.assertEqual(self.endpoint._token, self.token) def test_build_uri_with_no_params(self): - result = self.endpoint._build_uri(['foo', 'bar']) + result = self.endpoint._build_uri(["foo", "bar"]) parsed = parse.urlparse(result) query_params = parse.parse_qs(parsed.query) self.assertEqual(parsed.scheme, SCHEME) - self.assertEqual(parsed.netloc, 'localhost:8500') - self.assertEqual(parsed.path, '/{0}/endpoint/foo/bar'.format(VERSION)) - self.assertDictEqual(query_params, {'token': [self.token]}) + self.assertEqual(parsed.netloc, "localhost:8500") + self.assertEqual(parsed.path, "/{0}/endpoint/foo/bar".format(VERSION)) + self.assertDictEqual(query_params, {"token": [self.token]}) def test_build_uri_with_params(self): - result = self.endpoint._build_uri(['foo', 'bar'], {'baz': 'qux'}) + result = self.endpoint._build_uri(["foo", "bar"], {"baz": "qux"}) parsed = parse.urlparse(result) query_params = parse.parse_qs(parsed.query) self.assertEqual(parsed.scheme, SCHEME) - self.assertEqual(parsed.netloc, 'localhost:8500') - self.assertEqual(parsed.path, '/{0}/endpoint/foo/bar'.format(VERSION)) - self.assertDictEqual(query_params, { - 'token': [self.token], - 'baz': ['qux'] - }) + self.assertEqual(parsed.netloc, "localhost:8500") + self.assertEqual(parsed.path, "/{0}/endpoint/foo/bar".format(VERSION)) + self.assertDictEqual(query_params, {"token": [self.token], "baz": ["qux"]}) class EndpointBuildURIWithDCAndTokenTests(unittest.TestCase): def setUp(self): self.adapter = adapters.Request() - self.base_uri = '{0}://localhost:8500/{1}'.format(SCHEME, VERSION) + self.base_uri = "{0}://localhost:8500/{1}".format(SCHEME, VERSION) self.dc = str(uuid.uuid4()) self.token = str(uuid.uuid4()) - self.endpoint = base.Endpoint(self.base_uri, self.adapter, self.dc, - self.token) + self.endpoint = base.Endpoint(self.base_uri, self.adapter, self.dc, self.token) def test_dc_assignment(self): self.assertEqual(self.endpoint._dc, self.dc) @@ -257,65 +227,54 @@ def test_token_assignment(self): self.assertEqual(self.endpoint._token, self.token) def test_build_uri_with_no_params(self): - result = self.endpoint._build_uri(['foo', 'bar']) + result = self.endpoint._build_uri(["foo", "bar"]) parsed = parse.urlparse(result) query_params = parse.parse_qs(parsed.query) self.assertEqual(parsed.scheme, SCHEME) - self.assertEqual(parsed.netloc, 'localhost:8500') - self.assertEqual(parsed.path, '/{0}/endpoint/foo/bar'.format(VERSION)) - self.assertDictEqual(query_params, { - 'dc': [self.dc], - 'token': [self.token] - }) + self.assertEqual(parsed.netloc, "localhost:8500") + self.assertEqual(parsed.path, "/{0}/endpoint/foo/bar".format(VERSION)) + self.assertDictEqual(query_params, {"dc": [self.dc], "token": [self.token]}) def test_build_uri_with_params(self): - result = self.endpoint._build_uri(['foo', 'bar'], {'baz': 'qux'}) + result = self.endpoint._build_uri(["foo", "bar"], {"baz": "qux"}) parsed = parse.urlparse(result) query_params = parse.parse_qs(parsed.query) self.assertEqual(parsed.scheme, SCHEME) - self.assertEqual(parsed.netloc, 'localhost:8500') - self.assertEqual(parsed.path, '/{0}/endpoint/foo/bar'.format(VERSION)) - self.assertDictEqual(query_params, { - 'dc': [self.dc], - 'token': [self.token], - 'baz': ['qux'] - }) + self.assertEqual(parsed.netloc, "localhost:8500") + self.assertEqual(parsed.path, "/{0}/endpoint/foo/bar".format(VERSION)) + self.assertDictEqual(query_params, {"dc": [self.dc], "token": [self.token], "baz": ["qux"]}) class EndpointGetTests(unittest.TestCase): def setUp(self): self.adapter = adapters.Request() - self.base_uri = '{0}://localhost:8500/{1}'.format(SCHEME, VERSION) + self.base_uri = "{0}://localhost:8500/{1}".format(SCHEME, VERSION) self.dc = str(uuid.uuid4()) self.token = str(uuid.uuid4()) - self.endpoint = base.Endpoint(self.base_uri, self.adapter, self.dc, - self.token) + self.endpoint = base.Endpoint(self.base_uri, self.adapter, self.dc, self.token) def test_get_200_returns_response_body(self): @httmock.all_requests def response_content(_url_unused, request): headers = { - 'X-Consul-Index': 4, - 'X-Consul-Knownleader': 'true', - 'X-Consul-Lastcontact': 0, - 'Date': 'Fri, 19 Dec 2014 20:44:28 GMT', - 'Content-Length': 13, - 'Content-Type': 'application/json' + "X-Consul-Index": 4, + "X-Consul-Knownleader": "true", + "X-Consul-Lastcontact": 0, + "Date": "Fri, 19 Dec 2014 20:44:28 GMT", + "Content-Length": 13, + "Content-Type": "application/json", } content = b'{"consul": []}' return httmock.response(200, content, headers, None, 0, request) with httmock.HTTMock(response_content): values = self.endpoint._get([str(uuid.uuid4())]) - self.assertEqual(values, {'consul': []}) + self.assertEqual(values, {"consul": []}) def test_get_404_returns_empty_list(self): @httmock.all_requests def response_content(_url_unused, request): - headers = { - 'content-length': 0, - 'content-type': 'text/plain; charset=utf-8' - } + headers = {"content-length": 0, "content-type": "text/plain; charset=utf-8"} return httmock.response(404, None, headers, None, 0, request) with httmock.HTTMock(response_content): @@ -326,37 +285,33 @@ def response_content(_url_unused, request): class EndpointGetListTests(unittest.TestCase): def setUp(self): self.adapter = adapters.Request() - self.base_uri = '{0}://localhost:8500/{1}'.format(SCHEME, VERSION) + self.base_uri = "{0}://localhost:8500/{1}".format(SCHEME, VERSION) self.dc = str(uuid.uuid4()) self.token = str(uuid.uuid4()) - self.endpoint = base.Endpoint(self.base_uri, self.adapter, self.dc, - self.token) + self.endpoint = base.Endpoint(self.base_uri, self.adapter, self.dc, self.token) def test_get_list_200_returns_response_body(self): @httmock.all_requests def response_content(_url_unused, request): headers = { - 'X-Consul-Index': 4, - 'X-Consul-Knownleader': 'true', - 'X-Consul-Lastcontact': 0, - 'Date': 'Fri, 19 Dec 2014 20:44:28 GMT', - 'Content-Length': 13, - 'Content-Type': 'application/json' + "X-Consul-Index": 4, + "X-Consul-Knownleader": "true", + "X-Consul-Lastcontact": 0, + "Date": "Fri, 19 Dec 2014 20:44:28 GMT", + "Content-Length": 13, + "Content-Type": "application/json", } content = b'{"consul": []}' return httmock.response(200, content, headers, None, 0, request) with httmock.HTTMock(response_content): values = self.endpoint._get_list([str(uuid.uuid4())]) - self.assertEqual(values, [{'consul': []}]) + self.assertEqual(values, [{"consul": []}]) def test_get_list_404_returns_empty_list(self): @httmock.all_requests def response_content(_url_unused, request): - headers = { - 'content-length': 0, - 'content-type': 'text/plain; charset=utf-8' - } + headers = {"content-length": 0, "content-type": "text/plain; charset=utf-8"} return httmock.response(404, None, headers, None, 0, request) with httmock.HTTMock(response_content): diff --git a/tests/test_base_model.py b/tests/test_base_model.py new file mode 100644 index 0000000..65111f6 --- /dev/null +++ b/tests/test_base_model.py @@ -0,0 +1,104 @@ +"""Tests for the Base Model""" +import unittest +import uuid + +from consulate.models import base + + +class TestModel(base.Model): + """Model to perform tests against""" + + __slots__ = ["id", "serial", "name", "value"] + __attributes__ = { + "id": { + "key": "ID", + "type": uuid.UUID, + "cast_from": str, + "cast_to": str, + }, + "serial": { + "key": "Serial", + "type": int, + "default": 0, + "required": True, + "validator": lambda v, _m: v >= 0, + }, + "name": {"key": "Name", "type": str, "required": True}, + "value": {"type": str}, + "type": {"key": "Type", "type": str, "enum": {"client", "server"}}, + } + + +class TestCase(unittest.TestCase): + def test_happy_case_with_defaults(self): + kwargs = {"id": uuid.uuid4(), "name": str(uuid.uuid4())} + model = TestModel(**kwargs) + for key, value in kwargs.items(): + self.assertEqual(getattr(model, key), value) + self.assertEqual(model.serial, 0) + + def test_happy_case_with_all_values(self): + kwargs = {"id": uuid.uuid4(), "serial": 1, "name": str(uuid.uuid4()), "value": str(uuid.uuid4())} + model = TestModel(**kwargs) + for key, value in kwargs.items(): + self.assertEqual(getattr(model, key), value) + + def test_cast_from_str(self): + expectation = uuid.uuid4() + kwargs = {"id": str(expectation), "name": str(uuid.uuid4())} + model = TestModel(**kwargs) + self.assertEqual(model.id, expectation) + + def test_validator_failure(self): + kwargs = {"id": uuid.uuid4(), "name": str(uuid.uuid4()), "serial": -1} + with self.assertRaises(ValueError): + TestModel(**kwargs) + + def test_type_failure(self): + kwargs = {"id": True, "name": str(uuid.uuid4())} + with self.assertRaises(TypeError): + TestModel(**kwargs) + + def test_missing_requirement(self): + with self.assertRaises(ValueError): + TestModel() + + def test_invalid_attribute(self): + kwargs = {"name": str(uuid.uuid4()), "foo": "bar"} + with self.assertRaises(AttributeError): + TestModel(**kwargs) + + def test_invalid_attribute_assignment(self): + kwargs = {"name": str(uuid.uuid4())} + model = TestModel(**kwargs) + with self.assertRaises(AttributeError): + model.foo = "bar" + + def test_invalid_enum_assignment(self): + kwargs = {"name": str(uuid.uuid4()), "type": "invalid"} + with self.assertRaises(ValueError): + TestModel(**kwargs) + + def test_cast_to_dict(self): + kwargs = { + "id": uuid.uuid4(), + "serial": 1, + "name": str(uuid.uuid4()), + "value": str(uuid.uuid4()), + "type": "client", + } + expectation = { + "ID": str(kwargs["id"]), + "Serial": kwargs["serial"], + "Name": kwargs["name"], + "value": kwargs["value"], + "Type": kwargs["type"], + } + model = TestModel(**kwargs) + self.assertDictEqual(dict(model), expectation) + + def test_cast_to_dict_only_requirements(self): + kwargs = {"serial": 1, "name": str(uuid.uuid4())} + expectation = {"Serial": kwargs["serial"], "Name": kwargs["name"]} + model = TestModel(**kwargs) + self.assertDictEqual(dict(model), expectation) diff --git a/tests/test_catalog.py b/tests/test_catalog.py new file mode 100644 index 0000000..c7991e9 --- /dev/null +++ b/tests/test_catalog.py @@ -0,0 +1,9 @@ +from . import base + + +class TestCatalog(base.TestCase): + def test_catalog_registration(self): + self.consul.catalog.register("test-service", address="10.0.0.1") + self.assertIn("test-service", [n["Node"] for n in self.consul.catalog.nodes()]) + self.consul.catalog.deregister("test-service") + self.assertNotIn("test-service", [n["Node"] for n in self.consul.catalog.nodes()]) diff --git a/tests/coordinate_tests.py b/tests/test_coordinate.py similarity index 99% rename from tests/coordinate_tests.py rename to tests/test_coordinate.py index 78cb463..3089d86 100644 --- a/tests/coordinate_tests.py +++ b/tests/test_coordinate.py @@ -1,5 +1,6 @@ from . import base + class TestCoordinate(base.TestCase): def test_coordinate(self): coordinates = self.consul.coordinate.nodes() diff --git a/tests/test_event.py b/tests/test_event.py new file mode 100644 index 0000000..8daa3ae --- /dev/null +++ b/tests/test_event.py @@ -0,0 +1,12 @@ +import uuid + +from . import base + + +class TestEvent(base.TestCase): + def test_fire(self): + event_name = "test-event-%s" % str(uuid.uuid4())[0:8] + response = self.consul.event.fire(event_name) + events = self.consul.event.list(event_name) + self.assertEqual(event_name, events.get("Name")) + self.assertEqual(response, events.get("ID")) diff --git a/tests/kv_tests.py b/tests/test_kv.py similarity index 57% rename from tests/kv_tests.py rename to tests/test_kv.py index 979d691..c48698a 100644 --- a/tests/kv_tests.py +++ b/tests/test_kv.py @@ -1,85 +1,60 @@ # -*- coding: utf-8 -*- import json import unittest -try: - from urllib import parse # Python 3 -except ImportError: # pragma: no cover - import urlparse as parse # Python 2 import uuid import httmock -from consulate import adapters, api, utils +from consulate import adapters, api from . import base -SCHEME = 'http' -VERSION = 'v1' - -ALL_DATA = (b'[{"CreateIndex":643,"ModifyIndex":643,"LockIndex":0,"Key":"bar",' - b'"Flags":0,"Value":"YmF6"},{"CreateIndex":669,"ModifyIndex":669,"' - b'LockIndex":0,"Key":"baz","Flags":0,"Value":"cXV4"},{"CreateIndex' - b'":666,"ModifyIndex":666,"LockIndex":0,"Key":"corgie","Flags":128' - b',"Value":"ZG9n"},{"CreateIndex":642,"ModifyIndex":642,"LockIndex' - b'":0,"Key":"foo","Flags":0,"Value":"YmFy"},{"CreateIndex":644,"Mo' - b'difyIndex":644,"LockIndex":0,"Key":"quz","Flags":0,"Value":"dHJ1' - b'ZQ=="}]') - -ALL_ITEMS = [{ - 'CreateIndex': 643, - 'Flags': 0, - 'Key': 'bar', - 'LockIndex': 0, - 'ModifyIndex': 643, - 'Value': 'baz' -}, { - 'CreateIndex': 669, - 'Flags': 0, - 'Key': 'baz', - 'LockIndex': 0, - 'ModifyIndex': 669, - 'Value': 'qux' -}, { - 'CreateIndex': 666, - 'Flags': 128, - 'Key': 'corgie', - 'LockIndex': 0, - 'ModifyIndex': 666, - 'Value': 'dog' -}, { - 'CreateIndex': 642, - 'Flags': 0, - 'Key': 'foo', - 'LockIndex': 0, - 'ModifyIndex': 642, - 'Value': 'bar' -}, { - 'CreateIndex': 644, - 'Flags': 0, - 'Key': 'quz', - 'LockIndex': 0, - 'ModifyIndex': 644, - 'Value': 'true' -}] +SCHEME = "http" +VERSION = "v1" + +ALL_DATA = ( + b'[{"CreateIndex":643,"ModifyIndex":643,"LockIndex":0,"Key":"bar",' + b'"Flags":0,"Value":"YmF6"},{"CreateIndex":669,"ModifyIndex":669,"' + b'LockIndex":0,"Key":"baz","Flags":0,"Value":"cXV4"},{"CreateIndex' + b'":666,"ModifyIndex":666,"LockIndex":0,"Key":"corgie","Flags":128' + b',"Value":"ZG9n"},{"CreateIndex":642,"ModifyIndex":642,"LockIndex' + b'":0,"Key":"foo","Flags":0,"Value":"YmFy"},{"CreateIndex":644,"Mo' + b'difyIndex":644,"LockIndex":0,"Key":"quz","Flags":0,"Value":"dHJ1' + b'ZQ=="}]' +) + +ALL_ITEMS = [ + {"CreateIndex": 643, "Flags": 0, "Key": "bar", "LockIndex": 0, "ModifyIndex": 643, "Value": "baz"}, + {"CreateIndex": 669, "Flags": 0, "Key": "baz", "LockIndex": 0, "ModifyIndex": 669, "Value": "qux"}, + {"CreateIndex": 666, "Flags": 128, "Key": "corgie", "LockIndex": 0, "ModifyIndex": 666, "Value": "dog"}, + {"CreateIndex": 642, "Flags": 0, "Key": "foo", "LockIndex": 0, "ModifyIndex": 642, "Value": "bar"}, + {"CreateIndex": 644, "Flags": 0, "Key": "quz", "LockIndex": 0, "ModifyIndex": 644, "Value": "true"}, +] @httmock.all_requests def kv_all_records_content(_url_unused, request): return httmock.response( - 200, ALL_DATA, { - 'X-Consul-Index': 4, - 'X-Consul-Knownleader': 'true', - 'X-Consul-Lastcontact': 0, - 'Date': 'Fri, 19 Dec 2014 20:44:28 GMT', - 'Content-Length': len(ALL_DATA), - 'Content-Type': 'application/json' - }, None, 0, request) + 200, + ALL_DATA, + { + "X-Consul-Index": 4, + "X-Consul-Knownleader": "true", + "X-Consul-Lastcontact": 0, + "Date": "Fri, 19 Dec 2014 20:44:28 GMT", + "Content-Length": len(ALL_DATA), + "Content-Type": "application/json", + }, + None, + 0, + request, + ) class KVTests(unittest.TestCase): def setUp(self): self.adapter = adapters.Request() - self.base_uri = '{0}://localhost:8500/{1}'.format(SCHEME, VERSION) + self.base_uri = "{0}://localhost:8500/{1}".format(SCHEME, VERSION) self.dc = str(uuid.uuid4()) self.token = str(uuid.uuid4()) self.kv = api.KV(self.base_uri, self.adapter, self.dc, self.token) @@ -90,7 +65,7 @@ def response_content(_url_unused, request): return httmock.response(200, None, {}, None, 0, request) with httmock.HTTMock(response_content): - self.assertIn('foo', self.kv) + self.assertIn("foo", self.kv) def test_contains_evaluates_false(self): @httmock.all_requests @@ -98,7 +73,7 @@ def response_content(_url_unused, request): return httmock.response(404, None, {}, None, 0, request) with httmock.HTTMock(response_content): - self.assertNotIn('foo', self.kv) + self.assertNotIn("foo", self.kv) def test_get_all_items(self): with httmock.HTTMock(kv_all_records_content): @@ -108,22 +83,22 @@ def test_get_all_items(self): def test_items(self): with httmock.HTTMock(kv_all_records_content): for index, row in enumerate(self.kv.items()): - value = {ALL_ITEMS[index]['Key']: ALL_ITEMS[index]['Value']} + value = {ALL_ITEMS[index]["Key"]: ALL_ITEMS[index]["Value"]} self.assertDictEqual(row, value) def test_iter(self): with httmock.HTTMock(kv_all_records_content): for index, row in enumerate(self.kv): - self.assertEqual(row, ALL_ITEMS[index]['Key']) + self.assertEqual(row, ALL_ITEMS[index]["Key"]) def test_iteritems(self): with httmock.HTTMock(kv_all_records_content): for index, row in enumerate(self.kv.iteritems()): - value = (ALL_ITEMS[index]['Key'], ALL_ITEMS[index]['Value']) + value = (ALL_ITEMS[index]["Key"], ALL_ITEMS[index]["Value"]) self.assertEqual(row, value) def test_keys(self): - expectation = [item['Key'] for item in ALL_ITEMS] + expectation = [item["Key"] for item in ALL_ITEMS] with httmock.HTTMock(kv_all_records_content): self.assertEqual(self.kv.keys(), expectation) @@ -134,7 +109,7 @@ def test_len(self): def test_values(self): with httmock.HTTMock(kv_all_records_content): for index, row in enumerate(self.kv.values()): - self.assertEqual(row, ALL_ITEMS[index]['Value']) + self.assertEqual(row, ALL_ITEMS[index]["Value"]) class TestKVGetWithNoKey(base.TestCase): @@ -150,7 +125,7 @@ def test_get_item_raises_key_error(self, key): class TestKVSet(base.TestCase): @base.generate_key def test_set_item_del_item(self, key): - self.consul.kv[key] = 'foo' + self.consul.kv[key] = "foo" del self.consul.kv[key] self.assertNotIn(key, self.consul.kv) @@ -161,24 +136,24 @@ def test_set_item_get_item_bool_value(self, key): @base.generate_key def test_set_path_with_value(self, key): - path = 'path/{0}/'.format(key) - self.consul.kv.set(path, 'bar') - self.assertEqual('bar', self.consul.kv[path[:-1]]) + path = "path/{0}/".format(key) + self.consul.kv.set(path, "bar") + self.assertEqual("bar", self.consul.kv[path[:-1]]) @base.generate_key def test_set_item_get_item_int_value(self, key): self.consul.kv[key] = 128 - self.assertEqual(self.consul.kv[key], '128') + self.assertEqual(self.consul.kv[key], "128") @base.generate_key - def test_set_item_get_item_str_value(self, key): - self.consul.kv[key] = b'foo' - self.assertEqual(self.consul.kv[key], 'foo') + def test_set_item_get_item_str_value_key(self, key): + self.consul.kv[key] = b"foo" + self.assertEqual(self.consul.kv[key], "foo") @base.generate_key def test_set_item_get_item_str_value_raw(self, key): - self.consul.kv[key] = 'foo' - self.assertEqual(self.consul.kv.get(key, raw=True), 'foo') + self.consul.kv[key] = "foo" + self.assertEqual(self.consul.kv.get(key, raw=True), "foo") @base.generate_key def test_set_get_bool_value(self, key): @@ -188,19 +163,19 @@ def test_set_get_bool_value(self, key): @base.generate_key def test_set_get_item_value(self, key): self.consul.kv.set(key, 128) - self.assertEqual(self.consul.kv.get(key), '128') + self.assertEqual(self.consul.kv.get(key), "128") @base.generate_key def test_set_item_get_item_str_value(self, key): - self.consul.kv.set(key, 'foo') - self.assertEqual(self.consul.kv.get(key), 'foo') + self.consul.kv.set(key, "foo") + self.assertEqual(self.consul.kv.get(key), "foo") @base.generate_key def test_set_item_get_record(self, key): - self.consul.kv.set_record(key, 12, 'record') + self.consul.kv.set_record(key, 12, "record") record = self.consul.kv.get_record(key) - self.assertEqual('record', record['Value']) - self.assertEqual(12, record['Flags']) + self.assertEqual("record", record["Value"]) + self.assertEqual(12, record["Flags"]) self.assertIsInstance(record, dict) @base.generate_key @@ -209,40 +184,33 @@ def test_get_record_fail(self, key): @base.generate_key def test_set_record_no_replace_get_item_str_value(self, key): - self.consul.kv.set(key, 'foo') - self.consul.kv.set_record(key, 0, 'foo', False) - self.assertEqual(self.consul.kv.get(key), 'foo') + self.consul.kv.set(key, "foo") + self.consul.kv.set_record(key, 0, "foo", False) + self.assertEqual(self.consul.kv.get(key), "foo") @base.generate_key def test_set_record_same_value_get_item_str_value(self, key): - self.consul.kv.set(key, 'foo') - self.consul.kv.set_record(key, 0, 'foo', True) - self.assertEqual(self.consul.kv.get(key), 'foo') + self.consul.kv.set(key, "foo") + self.consul.kv.set_record(key, 0, "foo", True) + self.assertEqual(self.consul.kv.get(key), "foo") @base.generate_key def test_set_item_get_item_dict_value(self, key): - value = {'foo': 'bar'} + value = {"foo": "bar"} expectation = json.dumps(value) self.consul.kv.set(key, value) self.assertEqual(self.consul.kv.get(key), expectation) - @unittest.skipIf(utils.PYTHON3, 'No unicode strings in Python3') - @base.generate_key - def test_set_item_get_item_unicode_value(self, key): - self.consul.kv.set(key, u'I like to ✈') - self.assertEqual(self.consul.kv.get(key), u'I like to ✈') - - @unittest.skipIf(not utils.PYTHON3, 'No native unicode strings in Python2') @base.generate_key def test_set_item_get_item_unicode_value(self, key): - self.consul.kv.set(key, 'I like to ✈') + self.consul.kv.set(key, "I like to ✈") response = self.consul.kv.get(key) - self.assertEqual(response, 'I like to ✈') + self.assertEqual(response, "I like to ✈") @base.generate_key def test_set_item_in_records(self, key): - self.consul.kv.set(key, 'zomg') - expectation = (key, 0, 'zomg') + self.consul.kv.set(key, "zomg") + expectation = (key, 0, "zomg") self.assertIn(expectation, self.consul.kv.records()) @base.generate_key @@ -257,8 +225,7 @@ class TestKVLocking(base.TestCase): @base.generate_key def test_acquire_and_release_lock(self, key): lock_key = str(uuid.uuid4())[0:8] - session_id = self.consul.session.create( - key, behavior='delete', ttl='60s') + session_id = self.consul.session.create(key, behavior="delete", ttl="60s") self.assertTrue(self.consul.kv.acquire_lock(lock_key, session_id)) self.assertTrue(self.consul.kv.release_lock(lock_key, session_id)) self.consul.session.destroy(session_id) @@ -266,9 +233,8 @@ def test_acquire_and_release_lock(self, key): @base.generate_key def test_acquire_and_release_lock(self, key): lock_key = str(uuid.uuid4())[0:8] - sid = self.consul.session.create(key, behavior='delete', ttl='60s') - sid2 = self.consul.session.create( - key + '2', behavior='delete', ttl='60s') + sid = self.consul.session.create(key, behavior="delete", ttl="60s") + sid2 = self.consul.session.create(key + "2", behavior="delete", ttl="60s") self.assertTrue(self.consul.kv.acquire_lock(lock_key, sid)) self.assertFalse(self.consul.kv.acquire_lock(lock_key, sid2)) self.assertTrue(self.consul.kv.release_lock(lock_key, sid)) @@ -279,9 +245,8 @@ def test_acquire_and_release_lock(self, key): def test_acquire_and_release_lock_with_value(self, key): lock_key = str(uuid.uuid4())[0:8] lock_value = str(uuid.uuid4()) - sid = self.consul.session.create(key, behavior='delete', ttl='60s') - sid2 = self.consul.session.create( - key + '2', behavior='delete', ttl='60s') + sid = self.consul.session.create(key, behavior="delete", ttl="60s") + sid2 = self.consul.session.create(key + "2", behavior="delete", ttl="60s") self.assertTrue(self.consul.kv.acquire_lock(lock_key, sid, lock_value)) self.assertEqual(self.consul.kv.get(lock_key), lock_value) self.assertFalse(self.consul.kv.acquire_lock(lock_key, sid2)) diff --git a/tests/lock_tests.py b/tests/test_lock.py similarity index 100% rename from tests/lock_tests.py rename to tests/test_lock.py diff --git a/tests/session_tests.py b/tests/test_session.py similarity index 61% rename from tests/session_tests.py rename to tests/test_session.py index 4edbad9..c05f0f3 100644 --- a/tests/session_tests.py +++ b/tests/test_session.py @@ -14,30 +14,25 @@ def tearDown(self): def test_session_create(self): name = str(uuid.uuid4())[0:8] - session_id = self.consul.session.create( - name, behavior='delete', ttl='60s') + session_id = self.consul.session.create(name, behavior="delete", ttl="60s") self.sessions.append(session_id) self.assertIsNotNone(session_id) def test_session_destroy(self): name = str(uuid.uuid4())[0:8] - session_id = self.consul.session.create( - name, behavior='delete', ttl='60s') + session_id = self.consul.session.create(name, behavior="delete", ttl="60s") self.consul.session.destroy(session_id) - self.assertNotIn(session_id, - [s.get('ID') for s in self.consul.session.list()]) + self.assertNotIn(session_id, [s.get("ID") for s in self.consul.session.list()]) def test_session_info(self): name = str(uuid.uuid4())[0:8] - session_id = self.consul.session.create( - name, behavior='delete', ttl='60s') + session_id = self.consul.session.create(name, behavior="delete", ttl="60s") result = self.consul.session.info(session_id) - self.assertEqual(session_id, result.get('ID')) + self.assertEqual(session_id, result.get("ID")) self.consul.session.destroy(session_id) def test_session_renew(self): name = str(uuid.uuid4())[0:8] - session_id = self.consul.session.create( - name, behavior='delete', ttl='60s') + session_id = self.consul.session.create(name, behavior="delete", ttl="60s") self.sessions.append(session_id) self.assertTrue(self.consul.session.renew(session_id)) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..db6e453 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,70 @@ +# coding=utf-8 +import unittest + +from consulate import exceptions, utils + + +class MaybeEncodeTestCase(unittest.TestCase): + def test_str_test(self): + self.assertEqual(utils.maybe_encode("foo"), b"foo") + + def test_byte_test(self): + self.assertEqual(utils.maybe_encode(b"bar"), b"bar") + + +class Response(object): + def __init__(self, status_code=200, body=b"content"): + self.status_code = status_code + self.body = body + + +class ResponseOkTestCase(unittest.TestCase): + def test_200(self): + self.assertTrue(utils.response_ok(Response(200, b"ok"))) + + def test_400(self): + with self.assertRaises(exceptions.ClientError): + utils.response_ok(Response(400, b"Bad request")) + + def test_401(self): + with self.assertRaises(exceptions.ACLDisabled): + utils.response_ok(Response(401, b"What ACL?")) + + def test_403(self): + with self.assertRaises(exceptions.Forbidden): + utils.response_ok(Response(403, b"No")) + + def test_404_not_raising(self): + self.assertFalse(utils.response_ok(Response(404, b"not found"))) + + def test_404_raising(self): + with self.assertRaises(exceptions.NotFound): + utils.response_ok(Response(404, b"Not Found"), True) + + def test_500(self): + with self.assertRaises(exceptions.ServerError): + utils.response_ok(Response(500, b"Opps")) + + +class ValidateGoDurationTestCase(unittest.TestCase): + def test_valid_values(self): + for value in {"5µs", "300ms", "-1.5h", "2h45m", "5m", "30s"}: + print("Testing {}".format(value)) + self.assertTrue(utils.validate_go_interval(value)) + + def test_invalid_values(self): + for value in {"100", "1 year", "5M", "30S"}: + print("Testing {}".format(value)) + self.assertFalse(utils.validate_go_interval(value)) + + +class ValidateURLTestCase(unittest.TestCase): + def test_valid_values(self): + for value in {"https://foo", "http://localhost/bar"}: + print("Testing {}".format(value)) + self.assertTrue(utils.validate_url(value)) + + def test_invalid_values(self): + for value in {"localhost", "a"}: + print("Testing {}".format(value)) + self.assertFalse(utils.validate_url(value)) diff --git a/tests/utils_tests.py b/tests/utils_tests.py index 1a0d8fd..2eaaa71 100644 --- a/tests/utils_tests.py +++ b/tests/utils_tests.py @@ -10,75 +10,68 @@ def urlencode_test(self): class MaybeEncodeTestCase(unittest.TestCase): - @unittest.skipUnless(utils.PYTHON3, 'Python3 Only') + @unittest.skipUnless(utils.PYTHON3, "Python3 Only") def str_test(self): - self.assertEqual(utils.maybe_encode('foo'), b'foo') + self.assertEqual(utils.maybe_encode("foo"), b"foo") - @unittest.skipUnless(utils.PYTHON3, 'Python3 Only') + @unittest.skipUnless(utils.PYTHON3, "Python3 Only") def byte_test(self): - self.assertEqual(utils.maybe_encode(b'bar'), b'bar') + self.assertEqual(utils.maybe_encode(b"bar"), b"bar") class Response(object): - def __init__(self, status_code=200, body=b'content'): + def __init__(self, status_code=200, body=b"content"): self.status_code = status_code self.body = body class ResponseOkTestCase(unittest.TestCase): - def test_200(self): - self.assertTrue(utils.response_ok(Response(200, b'ok'))) + self.assertTrue(utils.response_ok(Response(200, b"ok"))) def test_400(self): with self.assertRaises(exceptions.ClientError): - utils.response_ok(Response(400, b'Bad request')) + utils.response_ok(Response(400, b"Bad request")) def test_401(self): with self.assertRaises(exceptions.ACLDisabled): - utils.response_ok(Response(401, b'What ACL?')) + utils.response_ok(Response(401, b"What ACL?")) def test_403(self): with self.assertRaises(exceptions.Forbidden): - utils.response_ok(Response(403, b'No')) + utils.response_ok(Response(403, b"No")) def test_404_not_raising(self): - self.assertFalse(utils.response_ok(Response(404, b'not found'))) + self.assertFalse(utils.response_ok(Response(404, b"not found"))) def test_404_raising(self): with self.assertRaises(exceptions.NotFound): - utils.response_ok(Response(404, b'Not Found'), True) + utils.response_ok(Response(404, b"Not Found"), True) def test_500(self): with self.assertRaises(exceptions.ServerError): - utils.response_ok(Response(500, b'Opps')) - - + utils.response_ok(Response(500, b"Opps")) class ValidateGoDurationTestCase(unittest.TestCase): - def test_valid_values(self): - for value in {'5µs', '300ms', '-1.5h', '2h45m', '5m', '30s'}: - print('Testing {}'.format(value)) + for value in {"5µs", "300ms", "-1.5h", "2h45m", "5m", "30s"}: + print("Testing {}".format(value)) self.assertTrue(utils.validate_go_interval(value)) def test_invalid_values(self): - for value in {'100', '1 year', '5M', '30S'}: - print('Testing {}'.format(value)) + for value in {"100", "1 year", "5M", "30S"}: + print("Testing {}".format(value)) self.assertFalse(utils.validate_go_interval(value)) class ValidateURLTestCase(unittest.TestCase): - def test_valid_values(self): - for value in {'https://foo', 'http://localhost/bar'}: - print('Testing {}'.format(value)) + for value in {"https://foo", "http://localhost/bar"}: + print("Testing {}".format(value)) self.assertTrue(utils.validate_url(value)) def test_invalid_values(self): - for value in {'localhost', 'a'}: - print('Testing {}'.format(value)) + for value in {"localhost", "a"}: + print("Testing {}".format(value)) self.assertFalse(utils.validate_url(value)) - -