commit 252a30c0876ab11839ae90174675aa3e822c6dd5 Author: Robert Kaussow Date: Mon Mar 30 23:28:34 2020 +0200 initial commit diff --git a/.drone.jsonnet b/.drone.jsonnet new file mode 100644 index 0000000..56297b4 --- /dev/null +++ b/.drone.jsonnet @@ -0,0 +1,283 @@ +local PythonVersion(pyversion='2.7') = { + name: 'python' + std.strReplace(pyversion, '.', '') + '-ansible', + image: 'python:' + pyversion, + environment: { + PY_COLORS: 1, + }, + commands: [ + 'pip install -r test-requirements.txt -qq', + 'pip install -qq .', + 'pytest corenetworks/tests/ --cov=corenetworks/ --no-cov-on-fail', + ], + depends_on: [ + 'clone', + ], +}; + +local PipelineLint = { + kind: 'pipeline', + name: 'lint', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + { + name: 'flake8', + image: 'python:3.8', + environment: { + PY_COLORS: 1, + }, + commands: [ + 'pip install -r test-requirements.txt -qq', + 'pip install -qq .', + 'flake8 ./corenetworks', + ], + }, + ], + trigger: { + ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'], + }, +}; + +local PipelineTest = { + kind: 'pipeline', + name: 'test', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + PythonVersion(pyversion='2.7'), + PythonVersion(pyversion='3.5'), + PythonVersion(pyversion='3.6'), + PythonVersion(pyversion='3.7'), + PythonVersion(pyversion='3.8'), + { + name: 'codecov', + image: 'python:3.8', + environment: { + PY_COLORS: 1, + CODECOV_TOKEN: { from_secret: 'codecov_token' }, + }, + commands: [ + 'pip install codecov', + 'codecov --required', + ], + }, + ], + depends_on: [ + 'dependencies', + ], + trigger: { + ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'], + }, +}; + +local PipelineSecurity = { + kind: 'pipeline', + name: 'security', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + { + name: 'bandit', + image: 'python:3.8', + environment: { + PY_COLORS: 1, + }, + commands: [ + 'pip install -r test-requirements.txt -qq', + 'pip install -qq .', + 'bandit -r ./corenetworks -x ./corenetworks/tests', + ], + }, + ], + depends_on: [ + 'test', + ], + trigger: { + ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'], + }, +}; + +local PipelineBuildPackage = { + kind: 'pipeline', + name: 'build-package', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + { + name: 'build', + image: 'python:3.8', + environment: { + SETUPTOOLS_SCM_PRETEND_VERSION: '${DRONE_TAG##v}', + }, + commands: [ + 'python setup.py sdist bdist_wheel', + ], + }, + { + name: 'checksum', + image: 'alpine', + commands: [ + 'cd dist/ && sha256sum * > ../sha256sum.txt', + ], + }, + { + name: 'publish-github', + image: 'plugins/github-release', + settings: { + overwrite: true, + api_key: { from_secret: 'github_token' }, + files: ['dist/*', 'sha256sum.txt'], + title: '${DRONE_TAG}', + note: 'CHANGELOG.md', + }, + when: { + ref: ['refs/tags/**'], + }, + }, + { + name: 'publish-pypi', + image: 'plugins/pypi', + settings: { + username: { from_secret: 'pypi_username' }, + password: { from_secret: 'pypi_password' }, + repository: 'https://upload.pypi.org/legacy/', + skip_build: true, + }, + when: { + ref: ['refs/tags/**'], + }, + }, + ], + depends_on: [ + 'security', + ], + trigger: { + ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'], + }, +}; + +local PipelineDocs = { + kind: 'pipeline', + name: 'docs', + platform: { + os: 'linux', + arch: 'amd64', + }, + concurrency: { + limit: 1, + }, + steps: [ + { + name: 'assets', + image: 'byrnedo/alpine-curl', + commands: [ + 'mkdir -p docs/themes/hugo-geekdoc/', + 'curl -L https://github.com/xoxys/hugo-geekdoc/releases/latest/download/hugo-geekdoc.tar.gz | tar -xz -C docs/themes/hugo-geekdoc/ --strip-components=1', + ], + }, + { + name: 'test', + image: 'klakegg/hugo:0.59.1-ext-alpine', + commands: [ + 'cd docs/ && hugo-official', + ], + }, + { + name: 'freeze', + image: 'appleboy/drone-ssh:1.5.5', + settings: { + host: { from_secret: 'ssh_host' }, + key: { from_secret: 'ssh_key' }, + script: [ + 'cp -R /var/www/virtual/geeklab/html/corenetworks.geekdocs.de/ /var/www/virtual/geeklab/html/corenetworks_freeze/', + 'ln -sfn /var/www/virtual/geeklab/html/corenetworks_freeze /var/www/virtual/geeklab/corenetworks.geekdocs.de', + ], + username: { from_secret: 'ssh_username' }, + }, + }, + { + name: 'publish', + image: 'appleboy/drone-scp', + settings: { + host: { from_secret: 'ssh_host' }, + key: { from_secret: 'ssh_key' }, + rm: true, + source: 'docs/public/*', + strip_components: 2, + target: '/var/www/virtual/geeklab/html/corenetworks.geekdocs.de/', + username: { from_secret: 'ssh_username' }, + }, + }, + { + name: 'cleanup', + image: 'appleboy/drone-ssh:1.5.5', + settings: { + host: { from_secret: 'ssh_host' }, + key: { from_secret: 'ssh_key' }, + script: [ + 'ln -sfn /var/www/virtual/geeklab/html/corenetworks.geekdocs.de /var/www/virtual/geeklab/corenetworks.geekdocs.de', + 'rm -rf /var/www/virtual/geeklab/html/corenetworks_freeze/', + ], + username: { from_secret: 'ssh_username' }, + }, + }, + ], + depends_on: [ + 'build-package', + 'build-container-amd64', + 'build-container-arm64', + 'build-container-arm', + ], + trigger: { + ref: ['refs/heads/master', 'refs/tags/**'], + }, +}; + +local PipelineNotifications = { + kind: 'pipeline', + name: 'notifications', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + { + name: 'matrix', + image: 'plugins/matrix', + settings: { + homeserver: { from_secret: 'matrix_homeserver' }, + roomid: { from_secret: 'matrix_roomid' }, + template: 'Status: **{{ build.status }}**
Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}
Message: {{ build.message }}', + username: { from_secret: 'matrix_username' }, + password: { from_secret: 'matrix_password' }, + }, + when: { + status: ['success', 'failure'], + }, + }, + ], + depends_on: [ + 'docs', + ], + trigger: { + ref: ['refs/heads/master', 'refs/tags/**'], + status: ['success', 'failure'], + }, +}; + +[ + PipelineLint, + PipelineTest, + PipelineSecurity, + PipelineBuildPackage, + PipelineDocs, + PipelineNotifications, +] diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..db740fe --- /dev/null +++ b/.drone.yml @@ -0,0 +1,308 @@ +--- +kind: pipeline +name: lint + +platform: + os: linux + arch: amd64 + +steps: +- name: flake8 + image: python:3.8 + commands: + - pip install -r test-requirements.txt -qq + - pip install -qq . + - flake8 ./corenetworks + environment: + PY_COLORS: 1 + +trigger: + ref: + - refs/heads/master + - refs/tags/** + - refs/pull/** + +--- +kind: pipeline +name: test + +platform: + os: linux + arch: amd64 + +steps: +- name: python27-ansible + image: python:2.7 + commands: + - pip install -r test-requirements.txt -qq + - pip install -qq . + - pytest corenetworks/tests/ --cov=corenetworks/ --no-cov-on-fail + environment: + PY_COLORS: 1 + depends_on: + - clone + +- name: python35-ansible + image: python:3.5 + commands: + - pip install -r test-requirements.txt -qq + - pip install -qq . + - pytest corenetworks/tests/ --cov=corenetworks/ --no-cov-on-fail + environment: + PY_COLORS: 1 + depends_on: + - clone + +- name: python36-ansible + image: python:3.6 + commands: + - pip install -r test-requirements.txt -qq + - pip install -qq . + - pytest corenetworks/tests/ --cov=corenetworks/ --no-cov-on-fail + environment: + PY_COLORS: 1 + depends_on: + - clone + +- name: python37-ansible + image: python:3.7 + commands: + - pip install -r test-requirements.txt -qq + - pip install -qq . + - pytest corenetworks/tests/ --cov=corenetworks/ --no-cov-on-fail + environment: + PY_COLORS: 1 + depends_on: + - clone + +- name: python38-ansible + image: python:3.8 + commands: + - pip install -r test-requirements.txt -qq + - pip install -qq . + - pytest corenetworks/tests/ --cov=corenetworks/ --no-cov-on-fail + environment: + PY_COLORS: 1 + depends_on: + - clone + +- name: codecov + image: python:3.8 + commands: + - pip install codecov + - codecov --required + environment: + CODECOV_TOKEN: + from_secret: codecov_token + PY_COLORS: 1 + +trigger: + ref: + - refs/heads/master + - refs/tags/** + - refs/pull/** + +depends_on: +- dependencies + +--- +kind: pipeline +name: security + +platform: + os: linux + arch: amd64 + +steps: +- name: bandit + image: python:3.8 + commands: + - pip install -r test-requirements.txt -qq + - pip install -qq . + - bandit -r ./corenetworks -x ./corenetworks/tests + environment: + PY_COLORS: 1 + +trigger: + ref: + - refs/heads/master + - refs/tags/** + - refs/pull/** + +depends_on: +- test + +--- +kind: pipeline +name: build-package + +platform: + os: linux + arch: amd64 + +steps: +- name: build + image: python:3.8 + commands: + - python setup.py sdist bdist_wheel + environment: + SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v} + +- name: checksum + image: alpine + commands: + - cd dist/ && sha256sum * > ../sha256sum.txt + +- name: publish-github + image: plugins/github-release + settings: + api_key: + from_secret: github_token + files: + - dist/* + - sha256sum.txt + note: CHANGELOG.md + overwrite: true + title: ${DRONE_TAG} + when: + ref: + - refs/tags/** + +- name: publish-pypi + image: plugins/pypi + settings: + password: + from_secret: pypi_password + repository: https://upload.pypi.org/legacy/ + skip_build: true + username: + from_secret: pypi_username + when: + ref: + - refs/tags/** + +trigger: + ref: + - refs/heads/master + - refs/tags/** + - refs/pull/** + +depends_on: +- security + +--- +kind: pipeline +name: docs + +platform: + os: linux + arch: amd64 + +concurrency: + limit: 1 + +steps: +- name: assets + image: byrnedo/alpine-curl + commands: + - mkdir -p docs/themes/hugo-geekdoc/ + - curl -L https://github.com/xoxys/hugo-geekdoc/releases/latest/download/hugo-geekdoc.tar.gz | tar -xz -C docs/themes/hugo-geekdoc/ --strip-components=1 + +- name: test + image: klakegg/hugo:0.59.1-ext-alpine + commands: + - cd docs/ && hugo-official + +- name: freeze + image: appleboy/drone-ssh:1.5.5 + settings: + host: + from_secret: ssh_host + key: + from_secret: ssh_key + script: + - cp -R /var/www/virtual/geeklab/html/corenetworks.geekdocs.de/ /var/www/virtual/geeklab/html/corenetworks_freeze/ + - ln -sfn /var/www/virtual/geeklab/html/corenetworks_freeze /var/www/virtual/geeklab/corenetworks.geekdocs.de + username: + from_secret: ssh_username + +- name: publish + image: appleboy/drone-scp + settings: + host: + from_secret: ssh_host + key: + from_secret: ssh_key + rm: true + source: docs/public/* + strip_components: 2 + target: /var/www/virtual/geeklab/html/corenetworks.geekdocs.de/ + username: + from_secret: ssh_username + +- name: cleanup + image: appleboy/drone-ssh:1.5.5 + settings: + host: + from_secret: ssh_host + key: + from_secret: ssh_key + script: + - ln -sfn /var/www/virtual/geeklab/html/corenetworks.geekdocs.de /var/www/virtual/geeklab/corenetworks.geekdocs.de + - rm -rf /var/www/virtual/geeklab/html/corenetworks_freeze/ + username: + from_secret: ssh_username + +trigger: + ref: + - refs/heads/master + - refs/tags/** + +depends_on: +- build-package +- build-container-amd64 +- build-container-arm64 +- build-container-arm + +--- +kind: pipeline +name: notifications + +platform: + os: linux + arch: amd64 + +steps: +- name: matrix + image: plugins/matrix + settings: + homeserver: + from_secret: matrix_homeserver + password: + from_secret: matrix_password + roomid: + from_secret: matrix_roomid + template: "Status: **{{ build.status }}**
Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}
Message: {{ build.message }}" + username: + from_secret: matrix_username + when: + status: + - success + - failure + +trigger: + ref: + - refs/heads/master + - refs/tags/** + status: + - success + - failure + +depends_on: +- docs + +--- +kind: signature +hmac: e0cd32befb8e01ec9cb6bd0e7bc032fe0816bcda9e16d89f37789a7db9ba9b82 + +... diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..764107f --- /dev/null +++ b/.flake8 @@ -0,0 +1,18 @@ +[flake8] +ignore = D103, D107, W503 +max-line-length = 99 +inline-quotes = double +exclude = + .git + .tox + __pycache__ + build + dist + tests + *.pyc + *.egg-info + .cache + .eggs + env* +application-import-names = corenetworks +format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3ffc9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,112 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ +env/ +env*/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +# Ignore ide addons +.server-script +.on-save.json +.vscode +.pytest_cache + +pip-wheel-metadata + +# Hugo documentation +docs/themes/ +docs/public/ +resources/_gen/ + +# Misc +.local/ +.corenetworks* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bb3e998 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Robert Kaussow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b68fe4a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# corenetworks + +Python library for the [https://core-networks.de](https://beta.api.core-networks.de/doc/) DNS API. diff --git a/corenetworks/__init__.py b/corenetworks/__init__.py new file mode 100644 index 0000000..634ff89 --- /dev/null +++ b/corenetworks/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Default package.""" + +from corenetworks.client import CoreNetworks # noqa + +__author__ = "Robert Kaussow" +__project__ = "corenetworks" +__license__ = "MIT" +__maintainer__ = "Robert Kaussow" +__email__ = "mail@geeklabor.de" +__url__ = "https://github.com/xoxys/corenetworks" +__version__ = "0.1.0" diff --git a/corenetworks/authenticators.py b/corenetworks/authenticators.py new file mode 100644 index 0000000..654e262 --- /dev/null +++ b/corenetworks/authenticators.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +"""Custom authenticators.""" + +import json + +from requests import ConnectionError +from requests import HTTPError +from requests import Request +from requests import Session +from requests.auth import AuthBase + +from .exceptions import AuthError +from .exceptions import CorenetworksError + + +class CoreNetworksBasicAuth(AuthBase): + """Define token based auth.""" + + def __init__(self, user, password, endpoint): + self.user = user + self.password = password + self.endpoint = endpoint + self.token = self._login() + + def __eq__(self, other): # noqa + return all([ + self.user == getattr(other, "user", None), + self.password == getattr(other, "password", None), + ]) + + def __ne__(self, other): # noqa + return not self == other + + def __call__(self, r): # noqa + r.headers["Authorization"] = "Bearer {0!s}".format(self.token) + return r + + def _login(self): + data = {} + data["login"] = self.user + data["password"] = self.password + + json_data = json.dumps(data) + url = "{endpoint}/auth/token".format(endpoint=self.endpoint) + + request = Request(method="POST", url=url, data=json_data) + prepared_request = request.prepare() + + try: + session = Session() + handle = session.send(prepared_request) + handle.raise_for_status() + except ConnectionError as e: + raise CorenetworksError( + "Server unreachable: {reason}".format(reason=e.message.reason), payload=e + ) + except HTTPError as e: + raise AuthError( + "Login failed: {code} {reason}".format( + code=e.response.status_code, reason=e.response.reason + ), + payload=e + ) + + response = handle.json() + + return response["token"] + + +class CoreNetworksTokenAuth(AuthBase): + """Define token based auth.""" + + def __init__(self, token): + self.token = token + + def __eq__(self, other): # noqa + return all([ + self.token == getattr(other, "api_token", None), + ]) + + def __ne__(self, other): # noqa + return not self == other + + def __call__(self, r): # noqa + r.headers["Authorization"] = "Bearer {0!s}".format(self.token) + return r diff --git a/corenetworks/client.py b/corenetworks/client.py new file mode 100644 index 0000000..6d93b4c --- /dev/null +++ b/corenetworks/client.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +"""API client.""" + +import copy +import json + +import jsonschema +from requests import ConnectionError +from requests import HTTPError +from requests import Request +from requests import Session +from six import iteritems + +import corenetworks + +from .authenticators import CoreNetworksBasicAuth +from .authenticators import CoreNetworksTokenAuth +from .exceptions import AuthError +from .exceptions import CorenetworksError +from .exceptions import ValidationError + + +class CoreNetworks(): + """Create authenticated API client.""" + + def __init__(self, user=None, password=None, api_token=None): + self.__endpoint = "https://beta.api.core-networks.de" + self.__user_agent = "Core Networks Python API {version}".format( + version=corenetworks.__version__ + ) + + self._schema = { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "ttl": { + "type": "number" + }, + "type": { + "type": "string" + }, + "data": { + "type": "string" + }, + }, + } + + if api_token: + self._auth = CoreNetworksTokenAuth(api_token) + else: + if not user or not password: + raise AuthError("Insufficient authentication details provided") + + self._auth = CoreNetworksBasicAuth(user, password, self.__endpoint) + + # RECORDS + + def records(self, zone, data={}): + """ + Get the list of records for the specific domain. + + Args: + zone (str): Name of the target DNS zone. + params (dict): Dictionary of filter parameters. + See https://beta.api.core-networks.de/doc/#functon_dnszones_records + but keep in mind that you have to pass a dict not a string. The required + filter string will be created automatically. + + Example: params={"type": ["NS", "SOA"]} will result in filter=?type[]=NS&type[]=SOA + + Returns: + list: List of entry dicts. + + """ + schema = copy.deepcopy(self._schema) + self.__validate(data, schema) + + filter_string = self.__json_to_filter(data) + result = self.__rest_helper( + "/dnszones/{zone}/records/{filter}".format(zone=zone, filter=filter_string), + method="GET" + ) + + return self.__normalize(result) + + def add_record(self, zone, data): + """ + Create a record for the given domain. + + Args: + param1: The first parameter. + param2: The second parameter. + + Returns: + True if successful, False otherwise. + + """ + schema = copy.deepcopy(self._schema) + schema["required"] = ["name", "type", "data"] + self.__validate(data, schema) + + result = self.__rest_helper( + "/dnszones/{zone}/records/".format(zone=zone), data=data, method="POST" + ) + + return self.__normalize(result) + + def delete_record(self, zone, data): + """ + Delete all DNS records of a zone that match the data. + + Args: + + """ + schema = copy.deepcopy(self._schema) + schema["properties"]["force_all"] = {"type": "boolean"} + schema["oneOf"] = [{ + "required": ["name"] + }, { + "required": ["type"] + }, { + "required": ["data"] + }, { + "required": ["force_all"] + }] + self.__validate(data, schema) + + if data.get("force_all"): + data = {} + + print(data) + + # result = self.__rest_helper( + # "/dnszones/{zone}/records/delete/".format(zone=zone), data=data, method="POST" + # ) + + # return self.__normalize(result) + + def __rest_helper(self, url, data=None, params=None, method="GET"): + """Handle requests to the Core Networks API.""" + url = self.__endpoint + url + headers = { + "User-Agent": self.__user_agent, + "Accept": "application/json", + "Content-Type": "application/json" + } + + if data: + json_data = json.dumps(data) + else: + json_data = None + + request = Request( + method=method, + url=url, + headers=headers, + data=json_data, + params=params, + auth=self._auth + ) + + prepared_request = request.prepare() + + r_json, r_headers = self.__request_helper(prepared_request) + + return r_json + + @staticmethod + def __request_helper(request): + """Handle firing off requests and exception raising.""" + try: + session = Session() + handle = session.send(request) + + handle.raise_for_status() + except ConnectionError as e: + raise CorenetworksError( + "Server unreachable: {reason}".format(reason=e.message.reason), payload=e + ) + except HTTPError as e: + raise CorenetworksError( + "Invalid response: {code} {reason}".format( + code=e.response.status_code, reason=e.response.reason + ), + payload=e + ) + + if handle.status_code == 200: + response = handle.json() + else: + response = [] + + return response, handle.headers + + @staticmethod + def __normalize(result): + if isinstance(result, list): + return [el for el in result] + elif isinstance(result, dict): + return result + else: + raise CorenetworksError("Unknown type: {}".format(type(result))) + + @staticmethod + def __json_to_filter(data): + filter_list = [] + + for (key, value) in iteritems(data): + if isinstance(value, list): + for item in value: + filter_list.append("{key}[]={value}".format(key=key, value=item)) + elif isinstance(value, str): + filter_list.append("{key}={value}".format(key=key, value=value)) + else: + raise CorenetworksError("Unknown type: {}".format(type(value))) + + filter_string = "&".join(filter_list) + + if filter_string: + filter_string = "?{filter}".format(filter=filter_string) + + return filter_string + + @staticmethod + def __validate(data, schema): + try: + jsonschema.validate(data, schema) + except jsonschema.exceptions.ValidationError as e: + raise ValidationError("Dataset invalid: {reason}".format(reason=e.message), payload=e) diff --git a/corenetworks/exceptions.py b/corenetworks/exceptions.py new file mode 100644 index 0000000..86d9daa --- /dev/null +++ b/corenetworks/exceptions.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +"""Custom package exceptions.""" + + +class CoreNetworksException(Exception): + """The main exception class.""" + + def __init__(self, message, payload=None): + self.message = message + self.payload = payload + + def __str__(self): # noqa + return str(self.message) + + +class CorenetworksError(CoreNetworksException): + """Authentication errors exception class.""" + + pass + + +class ValidationError(CoreNetworksException): + """Authentication errors exception class.""" + + pass + + +class AuthError(CoreNetworksException): + """Authentication errors exception class.""" + + pass diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ddb705b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[metadata] +description-file = README.md +license_file = LICENSE + +[bdist_wheel] +universal = 1 + +[isort] +default_section = THIRDPARTY +known_first_party = corenetworks +sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER +force_single_line = true +line_length = 99 +skip_glob = **/.env*,**/env/*,**/docs/* + +[yapf] +based_on_style = google +column_limit = 99 +dedent_closing_brackets = true +coalesce_brackets = true +split_before_logical_operator = true + +[tool:pytest] +filterwarnings = + ignore::FutureWarning + ignore:.*collections.*:DeprecationWarning + ignore:.*pep8.*:FutureWarning diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..97fdc02 --- /dev/null +++ b/setup.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +"""Setup script for the package.""" + +import io +import os +import re + +from setuptools import find_packages +from setuptools import setup + +PACKAGE_NAME = "corenetworks" + + +def get_property(prop, project): + current_dir = os.path.dirname(os.path.realpath(__file__)) + result = re.search( + r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop), + open(os.path.join(current_dir, project, "__init__.py")).read(), + ) + return result.group(1) + + +def get_readme(filename="README.md"): + this = os.path.abspath(os.path.dirname(__file__)) + with io.open(os.path.join(this, filename), encoding="utf-8") as f: + long_description = f.read() + return long_description + + +setup( + name=get_property("__project__", PACKAGE_NAME), + description="Python API client for Domain Management Automation " + "with Core Networks https://www.core-networks.de/", + keywords="dns, automation, nameserver, corenetworks", + version=get_property("__version__", PACKAGE_NAME), + author=get_property("__author__", PACKAGE_NAME), + author_email=get_property("__email__", PACKAGE_NAME), + url=get_property("__url__", PACKAGE_NAME), + license=get_property("__license__", PACKAGE_NAME), + long_description=get_readme(), + long_description_content_type="text/markdown", + packages=find_packages(exclude=["*.tests", "tests", "tests.*"]), + include_package_data=True, + zip_safe=False, + python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,<4", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Operating System :: POSIX", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Topic :: Software Development", + ], + install_requires=["six", "jsonschema"], + dependency_links=[], + test_suite="tests", +) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..95eaaaf --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,18 @@ +pipenv-setup +pydocstyle<4.0.0 +flake8 +flake8-colors +flake8-blind-except +flake8-builtins +flake8-docstrings<=3.0.0 +flake8-isort +flake8-logging-format +flake8-polyfill +flake8-quotes +pep8-naming +pytest +pytest-mock +pytest-cov +bandit +autopep8 +yapf