diff --git a/.git_hooks_pre-commit b/.git_hooks_pre-commit new file mode 100755 index 0000000..7938eee --- /dev/null +++ b/.git_hooks_pre-commit @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +HOOKS=$(dirname "$0") +GIT=$(dirname "$HOOKS") +ROOT=$(dirname "$GIT") + +. "$ROOT/env/bin/activate" +"$ROOT/script/lint" +"$ROOT/script/format" --check --quiet || (echo "Formatting check failed, run ./script/format" && exit 1) +"$ROOT/script/coverage" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..97e5f66 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,33 @@ +name: octoDNS SpfSource +on: [pull_request, workflow_dispatch] + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Tested versions based on dates in https://devguide.python.org/versions/#versions + python-version: ['3.8', '3.9', '3.10', '3.11'] + steps: + - uses: actions/checkout@v2 + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + - name: CI Build + run: | + ./script/cibuild + setup-py: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: '3.11' + architecture: x64 + - name: CI setup.py + run: | + ./script/cibuild-setup-py diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..0bea4de --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,13 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '42 4 * * *' +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v4 + with: + stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 7 days.' + days-before-stale: 90 + days-before-close: 7 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f6ec22 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.pyc +.coverage +.eggs/ +.env +/build/ +/config/ +coverage.xml +dist/ +env/ +htmlcov/ +nosetests.xml +octodns_*.egg-info/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d1360a5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +## TODO: v0.0.1 - 20??-??-?? - Moving + +#### Nothworthy Changes + +* Initial extraction of SpfSource from octoDNS core + +TODO: anything else + +#### Stuff + +TODO: anything else diff --git a/README.md b/README.md new file mode 100644 index 0000000..b206cb8 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +TODO: Review this README and add or modify as necessary. + +## SPF Value Management provider for octoDNS + +An [octoDNS](https://github.com/octodns/octodns/) provider that targets [SPF Value Management](https://github.com/octodns/octodns-spf). + +### Installation + +#### Command line + +``` +pip install octodns-spf +``` + +#### requirements.txt/setup.py + +Pinning specific versions or SHAs is recommended to avoid unplanned upgrades. + +##### Versions + +``` +# Start with the latest versions and don't just copy what's here +octodns==0.9.14 +octodns-spf==0.0.1 +``` + +##### SHAs + +``` +# Start with the latest/specific versions and don't just copy what's here +-e git+https://git@github.com/octodns/octodns.git@9da19749e28f68407a1c246dfdf65663cdc1c422#egg=octodns +-e git+https://git@github.com/octodns/octodns-spf.git@ec9661f8b335241ae4746eea467a8509205e6a30#egg=octodns_spf +``` + +### Configuration + +```yaml +providers: + spf: + class: octodns_spf.SpfSource + # TODO +``` + +### Support Information + +#### Records + +TODO: All octoDNS record types are supported. + +#### Dynamic + +TODO: SpfSource does not support dynamic records. + +### Development + +See the [/script/](/script/) directory for some tools to help with the development process. They generally follow the [Script to rule them all](https://github.com/github/scripts-to-rule-them-all) pattern. Most useful is `./script/bootstrap` which will create a venv and install both the runtime and development related requirements. It will also hook up a pre-commit hook that covers most of what's run by CI. + +TODO: any provider specific setup, a docker compose to run things locally etc? diff --git a/octodns_spf/__init__.py b/octodns_spf/__init__.py new file mode 100644 index 0000000..c1c07ce --- /dev/null +++ b/octodns_spf/__init__.py @@ -0,0 +1,12 @@ +# +# +# + +from octodns.provider.base import BaseProvider + +__VERSION__ = '0.0.1' + + +class SpfSource(BaseProvider): + # TODO: implement things + pass diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..745151f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[tool.black] +line-length=80 +skip-string-normalization=true +skip-magic-trailing-comma=true + +[tool.isort] +profile = "black" +known_first_party="octodns_spf" +known_octodns="octodns" +line_length=80 +sections="FUTURE,STDLIB,THIRDPARTY,OCTODNS,FIRSTPARTY,LOCALFOLDER" + +[tool.pytest.ini_options] +log_level = 'DEBUG' +pythonpath = "." diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..cc7e534 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,44 @@ +# DO NOT EDIT THIS FILE DIRECTLY - use ./script/update-requirements to update +Pygments==2.15.1 +black==23.3.0 +bleach==6.0.0 +build==0.10.0 +certifi==2023.5.7 +cffi==1.15.1 +charset-normalizer==3.1.0 +click==8.1.3 +cmarkgfm==2022.10.27 +coverage==7.2.7 +docutils==0.20.1 +exceptiongroup==1.1.1 +importlib-metadata==6.7.0 +iniconfig==2.0.0 +isort==5.12.0 +jaraco.classes==3.2.3 +keyring==24.2.0 +markdown-it-py==3.0.0 +mdurl==0.1.2 +more-itertools==9.1.0 +mypy-extensions==1.0.0 +packaging==23.1 +pathspec==0.11.1 +pkginfo==1.9.6 +platformdirs==3.8.0 +pluggy==1.2.0 +pycparser==2.21 +pyflakes==3.0.1 +pyproject_hooks==1.0.0 +pytest-cov==4.1.0 +pytest-network==0.0.1 +pytest==7.4.0 +readme-renderer==40.0 +requests-toolbelt==1.0.0 +requests==2.31.0 +rfc3986==2.0.0 +rich==13.4.2 +tomli==2.0.1 +twine==4.0.2 +typing_extensions==4.7.0 +urllib3==2.0.3 +webencodings==0.5.1 +zipp==3.15.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b0e119d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +# TODO: run ./script/update-requirements to fill this and requirements-dev.txt out +# DO NOT EDIT THIS FILE DIRECTLY - use ./script/update-requirements to update +PyYAML==6.0 +dnspython==2.3.0 +fqdn==1.5.1 +idna==3.4 +natsort==8.4.0 +octodns==0.9.21 +python-dateutil==2.8.2 +six==1.16.0 diff --git a/script/automations/do-patch-and-update-requirements.sh b/script/automations/do-patch-and-update-requirements.sh new file mode 100755 index 0000000..e82fdb8 --- /dev/null +++ b/script/automations/do-patch-and-update-requirements.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# patches for use with the script can likely be created with the following +# git format-patch -1 [sha] + +set -e + +PATCH="$HOME/octodns/octodns-template/tmp/0001-Update-CI-python-versions-remove-3.7.patch" +BRANCH="python-versions" + +# make sure we're on main to start +(test $(git rev-parse --abbrev-ref HEAD) != "main" && echo "on branch" && exit 1 || exit 0) + +# make sure we're completely up to date with origin +git pull + +# create our branch +git checkout -b $BRANCH + +# make our patch +patch -p1 --no-backup-if-mismatch < $PATCH + +# add and comment changes +git add -p .github/ +git commit -m "update CI python versions, remove 3.7" + +git add -p setup.py +git commit -m "update setup.py requirement versions now that 3.7 is gone" + +# update requirements +./script/update-requirements + +# re-bootstrap to make sure those versions are installed +./script/bootstrap + +# make any formatting changes +./script/format + +# show any lint errors +./script/lint + +# add and comment requirements changes +git add -p requirements*.txt +git commit -m "update requirements*.txt" + +# if there's any formatting changes add them and then commit them, there +# shouldn't be anything else happening here, if you make non-formatting changes +# for some reason commit those manually +if ! git status --porcelain; then + git add -p + git commit -m "updated black formatting" +fi + +# push our current branch +git push -u origin $BRANCH + +# and open a PR, this assumes your local handle matches your github handle, if +# not set USER= before running the script +hub pull-request --browse --file /tmp/body.txt -b main -h $BRANCH -a $USER diff --git a/script/automations/do-patch.sh b/script/automations/do-patch.sh new file mode 100755 index 0000000..574b5bf --- /dev/null +++ b/script/automations/do-patch.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# patches for use with the script can likely be created with the following +# git format-patch -1 [sha] + +set -e + +PATCH="$1" +BRANCH="$2" +COMMIT_MESSAGE="$3" +PR_MESSAGE="$4" + +if [[ -z "$PR_MESSAGE" || ! -e "$PR_MESSAGE" ]]; then + echo "incorrect usage" + exit 1 +fi + +# make sure we don't have uncommited changes +([[ `git status --porcelain` ]] && echo "local changes" && exit 1 || exit 0) + +# make sure we're on main to start +(test $(git rev-parse --abbrev-ref HEAD) != "main" && echo "on branch" && exit 1 || exit 0) + +# make sure we're completely up to date with origin +git pull + +# create our branch +git checkout -b $BRANCH + +# make our patch +patch -p1 --no-backup-if-mismatch < $PATCH + +# add and comment changes +# TODO: this doesn't support newly added files... +git add -p +git commit -m "$COMMIT_MESSAGE" + +# push our current branch +git push -u origin $BRANCH + +# and open a PR, this assumes your local handle matches your github handle, if +# not set USER= before running the script +hub pull-request --browse --file $PR_MESSAGE -b main -h $BRANCH -a $USER diff --git a/script/automations/make-updates.sh b/script/automations/make-updates.sh new file mode 100755 index 0000000..2f6fff7 --- /dev/null +++ b/script/automations/make-updates.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -e + +if [ -z "$1" ]; then + if ! git diff --no-ext-diff --quiet --exit-code; then + echo "local changes" + exit 1 + fi + + if test $(git rev-parse --abbrev-ref HEAD) != "main"; then + echo "on branch" + exit 1 + fi + + git pull + git checkout -b isort +fi + + +MODULE=$(basename "$PWD" | sed 's/-/_/') + +sed -e s/octodns_spf/$MODULE/ ~/octodns/octodns-template/pyproject.toml > pyproject.toml + +TMP=$(mktemp) +awk "/build.*/ {print; print \" # >=5.12.0 does not support python 3.7, we still do\"; print \" 'isort==5.11.5',\"; next }1" ./setup.py > $TMP +mv $TMP setup.py + +perl -i -p0e 's/export PYTHONPATH.*\npytest/pytest/se' script/coverage +perl -i -p0e 's/export PYTHONPATH.*\npytest/pytest/se' script/test + +cp ~/octodns/octodns-template/script/format script/format + +source env/bin/activate + +./script/update-requirements +deactivate +rm -rf env/ +./script/bootstrap + +source env/bin/activate + +git add . +git commit -m "Add isort, use pyproject.toml for isort and black" --no-verify + +./script/format + +git add . +git commit -m "isort formatting" + +echo "# Commit for isort formatting changes" >> .git-blame-ignore-revs +git rev-parse --verify HEAD >> .git-blame-ignore-revs + +git add . +git commit -m "ignore isort commit in blame" --no-verify + +git pob + +hub pull-request --file /tmp/body.txt -b main -h isort -a ross diff --git a/script/automations/update-requirements.sh b/script/automations/update-requirements.sh new file mode 100755 index 0000000..cdfaa40 --- /dev/null +++ b/script/automations/update-requirements.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +set -e + +if [ -n "$1" ]; then + MSG="$1" +else + MSG="update requirements*.txt" +fi + +# make sure we don't have uncommited changes +([[ `git status --porcelain` ]] && echo "local changes" && exit 1 || exit 0) + +# make sure we're on main to start +(test $(git rev-parse --abbrev-ref HEAD) != "main" && echo "on branch" && exit 1 || exit 0) + +# make sure we're completely up to date with origin +git pull + +# create our branch +BRANCH=update-requirements +git checkout -b $BRANCH + +# update requirements +./script/update-requirements + +# re-bootstrap to make sure those versions are installed +./script/bootstrap + +# make any formatting changes +./script/format + +# show any lint errors +./script/lint + +# add and comment requirements changes +git add -p requirements*.txt +git commit -m "$1" + +# if there's any formatting changes add them and then commit them, there +# shouldn't be anything else happening here, if you make non-formatting changes +# for some reason commit those manually +if ! git status --porcelain; then + git add -p + git commit -m "updated black formatting" +fi + +# push our current branch +git push -u origin $BRANCH + +# and open a PR, this assumes your local handle matches your github handle, if +# not set USER= before running the script +hub pull-request --browse --file /tmp/body.txt -b main -h $BRANCH -a $USER diff --git a/script/bootstrap b/script/bootstrap new file mode 100755 index 0000000..a7d85e7 --- /dev/null +++ b/script/bootstrap @@ -0,0 +1,48 @@ +#!/bin/bash +# Usage: script/bootstrap +# Ensures all dependencies are installed locally. + +set -e + +cd "$(dirname "$0")"/.. +ROOT=$(pwd) + +if [ -z "$VENV_NAME" ]; then + VENV_NAME="env" +fi + +if [ ! -d "$VENV_NAME" ]; then + if [ -z "$VENV_PYTHON" ]; then + VENV_PYTHON=$(command -v python3) + fi + "$VENV_PYTHON" -m venv "$VENV_NAME" +fi +. "$VENV_NAME/bin/activate" + +# We're in the venv now, so use the first Python in $PATH. In particular, don't +# use $VENV_PYTHON - that's the Python that *created* the venv, not the python +# *inside* the venv +python -m pip install -U 'pip>=10.0.1' +python -m pip install -r requirements.txt + +if [ "$ENV" != "production" ]; then + python -m pip install -r requirements-dev.txt +fi + +if [ -d ".git" ]; then + if [ -f ".git-blame-ignore-revs" ]; then + echo "" + echo "Setting blame.ignoreRevsFile to .git-blame-ingore-revs" + git config --local blame.ignoreRevsFile .git-blame-ignore-revs + fi + if [ ! -L ".git/hooks/pre-commit" ]; then + echo "" + echo "Installing pre-commit hook" + ln -s "$ROOT/.git_hooks_pre-commit" ".git/hooks/pre-commit" + fi +fi + +echo "" +echo "Run source env/bin/activate to get your shell in to the virtualenv" +echo "See README.md for more information." +echo "" diff --git a/script/cibuild b/script/cibuild new file mode 100755 index 0000000..e4763b6 --- /dev/null +++ b/script/cibuild @@ -0,0 +1,30 @@ +#!/bin/sh +set -e + +cd "$(dirname "$0")/.." + +echo "## bootstrap ###################################################################" +script/bootstrap + +if [ -z "$VENV_NAME" ]; then + VENV_NAME="env" +fi + +. "$VENV_NAME/bin/activate" + +echo "## environment & versions ######################################################" +python --version +pip --version + +echo "## clean up ####################################################################" +find octodns_spf tests* -name "*.pyc" -exec rm {} \; +rm -f *.pyc +echo "## begin #######################################################################" +# For now it's just lint... +echo "## lint ########################################################################" +script/lint +echo "## formatting ##################################################################" +script/format --check || (echo "Formatting check failed, run ./script/format" && exit 1) +echo "## tests/coverage ##############################################################" +script/coverage +echo "## complete ####################################################################" diff --git a/script/cibuild-setup-py b/script/cibuild-setup-py new file mode 100755 index 0000000..ea04bfa --- /dev/null +++ b/script/cibuild-setup-py @@ -0,0 +1,22 @@ +#!/bin/sh +set -e + +cd "$(dirname "$0")/.." + +echo "## create test venv ############################################################" +TMP_DIR=$(mktemp -d -t ci-XXXXXXXXXX) +python3 -m venv $TMP_DIR +. "$TMP_DIR/bin/activate" +echo "## environment & versions ######################################################" +python --version +pip --version +echo "## validate setup.py build #####################################################" +python setup.py build +echo "## validate setup.py install ###################################################" +python setup.py install +echo "## installed module versions ###################################################" +pip freeze +echo "## validate tests can run against installed code ###############################" +pip install pytest pytest-network +pytest --disable-network +echo "## complete ####################################################################" diff --git a/script/coverage b/script/coverage new file mode 100755 index 0000000..87e037b --- /dev/null +++ b/script/coverage @@ -0,0 +1,37 @@ +#!/bin/sh +set -e + +cd "$(dirname "$0")/.." + +if [ -z "$VENV_NAME" ]; then + VENV_NAME="env" +fi + +ACTIVATE="$VENV_NAME/bin/activate" +if [ ! -f "$ACTIVATE" ]; then + echo "$ACTIVATE does not exist, run ./script/bootstrap" >&2 + exit 1 +fi +. "$ACTIVATE" + +SOURCE_DIR="octodns_spf/" + +# Don't allow disabling coverage +grep -r -I --line-number "# pragma: +no.*cover" $SOURCE_DIR && { + echo "Code coverage should not be disabled" + exit 1 +} + +# TODO: ensure any common env var secrets have been cleared +export TODO_ACCESS_KEY_ID= + +pytest \ + --disable-network \ + --cov-reset \ + --cov=$SOURCE_DIR \ + --cov-fail-under=100 \ + --cov-report=html \ + --cov-report=xml \ + --cov-report=term \ + --cov-branch \ + "$@" diff --git a/script/format b/script/format new file mode 100755 index 0000000..d936d90 --- /dev/null +++ b/script/format @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +SOURCES="$(find *.py octodns_spf tests -name "*.py") $(grep --files-with-matches '^#!.*python' script/*)" + +. env/bin/activate + +isort "$@" $SOURCES +black "$@" $SOURCES diff --git a/script/lint b/script/lint new file mode 100755 index 0000000..d85b809 --- /dev/null +++ b/script/lint @@ -0,0 +1,20 @@ +#!/bin/sh +set -e + +cd "$(dirname "$0")/.." +ROOT=$(pwd) + +if [ -z "$VENV_NAME" ]; then + VENV_NAME="env" +fi + +ACTIVATE="$VENV_NAME/bin/activate" +if [ ! -f "$ACTIVATE" ]; then + echo "$ACTIVATE does not exist, run ./script/bootstrap" >&2 + exit 1 +fi +. "$ACTIVATE" + +SOURCES="$(find *.py octodns_spf tests -name "*.py") $(grep --files-with-matches '^#!.*python' script/*)" + +pyflakes $SOURCES diff --git a/script/release b/script/release new file mode 100755 index 0000000..35b6419 --- /dev/null +++ b/script/release @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e +set -o pipefail + +cd "$(dirname "$0")"/.. +ROOT=$(pwd) + +if [ -z "$VENV_NAME" ]; then + VENV_NAME="env" +fi + +PYPYRC="$HOME/.pypirc" +if [ ! -e "$PYPYRC" ]; then + cat << EndOfMessage >&2 +$PYPYRC does not exist, please create it with the following contents + +[pypi] + username = __token__ + password = [secret-token-goes-here] + +EndOfMessage + exit 1 +fi + +ACTIVATE="$VENV_NAME/bin/activate" +if [ ! -f "$ACTIVATE" ]; then + echo "$ACTIVATE does not exist, run ./script/bootstrap" >&2 + exit 1 +fi +. "$ACTIVATE" + +# Set so that setup.py will create a public release style version number +export OCTODNS_RELEASE=1 + +VERSION="$(grep "^__VERSION__" "$ROOT/octodns_spf/__init__.py" | sed -e "s/.* = '//" -e "s/'$//")" + +git tag -s "v$VERSION" -m "Release $VERSION" +git push origin "v$VERSION" +echo "Tagged and pushed v$VERSION" +python -m build --sdist --wheel +twine check dist/*$VERSION.tar.gz dist/*$VERSION*.whl +twine upload dist/*$VERSION.tar.gz dist/*$VERSION*.whl +echo "Uploaded $VERSION" diff --git a/script/sdist b/script/sdist new file mode 100755 index 0000000..6d3e053 --- /dev/null +++ b/script/sdist @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e +set -o pipefail + +cd "$(dirname "$0")"/.. +ROOT=$(pwd) + +if ! git diff-index --quiet HEAD --; then + echo "Changes in local directory, commit or clear" >&2 + exit 1 +fi + +VERSION="$(grep __VERSION__ "$ROOT/octodns_spf/__init__.py" | sed -e "s/.* = '//" -e "s/'$//")" +SHA=$(git rev-parse HEAD) +python setup.py sdist +TARBALL="dist/octodns-spf-$SHA.tar.gz" +mv dist/octodns-spf-$VERSION.tar.gz "$TARBALL" + +echo "Created $TARBALL" diff --git a/script/test b/script/test new file mode 100755 index 0000000..bf031d6 --- /dev/null +++ b/script/test @@ -0,0 +1,20 @@ +#!/bin/sh +set -e + +cd "$(dirname "$0")/.." + +if [ -z "$VENV_NAME" ]; then + VENV_NAME="env" +fi + +ACTIVATE="$VENV_NAME/bin/activate" +if [ ! -f "$ACTIVATE" ]; then + echo "$ACTIVATE does not exist, run ./script/bootstrap" >&2 + exit 1 +fi +. "$ACTIVATE" + +# TODO: ensure any common env var secrets have been cleared +export TODO_ACCESS_KEY_ID= + +pytest --disable-network "$@" diff --git a/script/update-requirements b/script/update-requirements new file mode 100755 index 0000000..7696f6d --- /dev/null +++ b/script/update-requirements @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +import re +from os.path import join +from subprocess import check_call, check_output +from sys import argv +from tempfile import TemporaryDirectory + + +def print_packages(packages, heading): + print(f'{heading}:') + print(' ', end='') + print('\n '.join(packages)) + + +# would be nice if there was a cleaner way to get this, but I've not found a +# more reliable one. +with open('setup.py') as fh: + match = re.search(r"name='(?P[\w-]+)',", fh.read()) + if not match: + raise Exception('failed to determine our package name') + our_package_name = match.group('pkg') + print(f'our_package_name: {our_package_name}') + +with TemporaryDirectory() as tmpdir: + check_call(['python3', '-m', 'venv', tmpdir]) + + # base needs + check_call([join(tmpdir, 'bin', 'pip'), 'install', '.']) + frozen = check_output([join(tmpdir, 'bin', 'pip'), 'freeze']) + frozen = set(frozen.decode('utf-8').strip().split('\n')) + + # dev additions + check_call([join(tmpdir, 'bin', 'pip'), 'install', '.[dev]']) + dev_frozen = check_output([join(tmpdir, 'bin', 'pip'), 'freeze']) + dev_frozen = set(dev_frozen.decode('utf-8').strip().split('\n')) - frozen + +# pip installs the module itself along with deps so we need to get that out of +# our list by finding the thing that was file installed during dev +frozen = sorted([p for p in frozen if not p.startswith(our_package_name)]) +dev_frozen = sorted( + [p for p in dev_frozen if not p.startswith(our_package_name)] +) + +print_packages(frozen, 'frozen') +print_packages(dev_frozen, 'dev_frozen') + +script = argv[0] + +with open('requirements.txt', 'w') as fh: + fh.write(f'# DO NOT EDIT THIS FILE DIRECTLY - use {script} to update\n') + fh.write('\n'.join(frozen)) + fh.write('\n') + +with open('requirements-dev.txt', 'w') as fh: + fh.write(f'# DO NOT EDIT THIS FILE DIRECTLY - use {script} to update\n') + fh.write('\n'.join(dev_frozen)) + fh.write('\n') diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a795a57 --- /dev/null +++ b/setup.py @@ -0,0 +1,76 @@ +from os import environ +from subprocess import CalledProcessError, check_output + +from setuptools import find_packages, setup + + +def descriptions(): + with open('README.md') as fh: + ret = fh.read() + first = ret.split('\n', 1)[0].replace('#', '') + return first, ret + + +def version(): + version = 'unknown' + with open('octodns_spf/__init__.py') as fh: + for line in fh: + if line.startswith('__VERSION__'): + version = line.split("'")[1] + break + + # pep440 style public & local version numbers + if environ.get('OCTODNS_RELEASE', False): + # public + return version + try: + sha = check_output(['git', 'rev-parse', 'HEAD']).decode('utf-8')[:8] + except (CalledProcessError, FileNotFoundError): + sha = 'unknown' + # local + return f'{version}+{sha}' + + +description, long_description = descriptions() + +tests_require = ( + 'pytest', + 'pytest-cov', + 'pytest-network', + # TODO: other test-time requirements +) + +setup( + author='Ross McFarland', + author_email='rwmcfa1@gmail.com', + description=description, + extras_require={ + 'dev': tests_require + + ( + # we need to manually/explicitely bump major versions as they're + # likely to result in formatting changes that should happen in their + # own PR. This will basically happen yearly + # https://black.readthedocs.io/en/stable/the_black_code_style/index.html#stability-policy + 'black>=23.1.0,<24.0.0', + 'build>=0.7.0', + 'isort>=5.11.5', + 'pyflakes>=2.2.0', + 'readme_renderer[md]>=26.0', + 'twine>=3.4.2', + ), + 'test': tests_require, + }, + install_requires=( + 'octodns>=0.9.14', + # TODO: other requirements + ), + license='MIT', + long_description=long_description, + long_description_content_type='text/markdown', + name='octodns-spf', + packages=find_packages(), + python_requires='>=3.6', + tests_require=tests_require, + url='https://github.com/octodns/octodns-spf', + version=version(), +) diff --git a/tests/test_provider_octodns_spf.py b/tests/test_provider_octodns_spf.py new file mode 100644 index 0000000..f4ee981 --- /dev/null +++ b/tests/test_provider_octodns_spf.py @@ -0,0 +1,14 @@ +# +# +# + +from unittest import TestCase + +from octodns_spf import SpfSource + + +class TestSpfSource(TestCase): + # TODO: test provider + def test_nothing(self): + self.assertTrue(True) + SpfSource