Add pyinvoke task

Ensure git tag matches package versions before uploading
This commit is contained in:
Vincent Maillol 2021-08-04 08:58:07 +02:00
parent 43d2789636
commit dbf1eb6ac4
7 changed files with 239 additions and 58 deletions

View File

@ -17,16 +17,9 @@ local BuildAndTestPipeline(name, image) = {
name: "Install package and test", name: "Install package and test",
image: image, image: image,
commands: [ commands: [
"echo Install package", "test \"$(md5sum tasks.py)\" = \"18f864b3ac76119938e3317e49b4ffa1 tasks.py\"",
"pip install -U setuptools wheel pip; pip install .", "pip install -U setuptools wheel pip; pip install invoke",
"echo Test to import module of package", "invoke prepare-upload"
"python -c \"import importlib, setuptools; [print(importlib.import_module(package).__name__, '[OK]') for package in setuptools.find_packages() if package.startswith('aiohttp_pydantic.') or package == 'aiohttp_pydantic']\"",
"echo Install CI dependencies",
"pip install -r requirements/ci.txt",
"echo Launch unittest",
"pytest --cov-report=xml --cov=aiohttp_pydantic tests/",
"echo Check the README.rst render",
"python -m readme_renderer -o /dev/null README.rst"
] ]
}, },
{ {
@ -56,7 +49,7 @@ local BuildAndTestPipeline(name, image) = {
steps: [ steps: [
{ {
name: "Install twine and deploy", name: "Install twine and deploy",
image: "python3.8", image: "python:3.8",
environment: { environment: {
pypi_username: { pypi_username: {
from_secret: 'pypi_username' from_secret: 'pypi_username'
@ -66,10 +59,9 @@ local BuildAndTestPipeline(name, image) = {
} }
}, },
commands: [ commands: [
"pip install --force-reinstall twine wheel", "test \"$(md5sum tasks.py)\" = \"18f864b3ac76119938e3317e49b4ffa1 tasks.py\"",
"python setup.py build bdist_wheel", "pip install -U setuptools wheel pip; pip install invoke",
"set +x", "invoke upload --pypi-user \"$pypi_username\" --pypi-password \"$pypi_password\""
"twine upload --non-interactive -u \"$pypi_username\" -p \"$pypi_password\" dist/*"
] ]
}, },
], ],

View File

@ -11,16 +11,9 @@ steps:
- name: Install package and test - name: Install package and test
image: python:3.8 image: python:3.8
commands: commands:
- echo Install package - test "$(md5sum tasks.py)" = "18f864b3ac76119938e3317e49b4ffa1 tasks.py"
- pip install -U setuptools wheel pip; pip install . - pip install -U setuptools wheel pip; pip install invoke
- echo Test to import module of package - invoke prepare-upload
- python -c "import importlib, setuptools; [print(importlib.import_module(package).__name__, '[OK]') for package in setuptools.find_packages() if package.startswith('aiohttp_pydantic.') or package == 'aiohttp_pydantic']"
- echo Install CI dependencies
- pip install -r requirements/ci.txt
- echo Launch unittest
- pytest --cov-report=xml --cov=aiohttp_pydantic tests/
- echo Check the README.rst render
- python -m readme_renderer -o /dev/null README.rst
- name: coverage - name: coverage
image: plugins/codecov image: plugins/codecov
@ -48,16 +41,9 @@ steps:
- name: Install package and test - name: Install package and test
image: python:3.9 image: python:3.9
commands: commands:
- echo Install package - test "$(md5sum tasks.py)" = "18f864b3ac76119938e3317e49b4ffa1 tasks.py"
- pip install -U setuptools wheel pip; pip install . - pip install -U setuptools wheel pip; pip install invoke
- echo Test to import module of package - invoke prepare-upload
- python -c "import importlib, setuptools; [print(importlib.import_module(package).__name__, '[OK]') for package in setuptools.find_packages() if package.startswith('aiohttp_pydantic.') or package == 'aiohttp_pydantic']"
- echo Install CI dependencies
- pip install -r requirements/ci.txt
- echo Launch unittest
- pytest --cov-report=xml --cov=aiohttp_pydantic tests/
- echo Check the README.rst render
- python -m readme_renderer -o /dev/null README.rst
- name: coverage - name: coverage
image: plugins/codecov image: plugins/codecov
@ -83,12 +69,11 @@ platform:
steps: steps:
- name: Install twine and deploy - name: Install twine and deploy
image: python3.8 image: python:3.8
commands: commands:
- pip install --force-reinstall twine wheel - test "$(md5sum tasks.py)" = "18f864b3ac76119938e3317e49b4ffa1 tasks.py"
- python setup.py build bdist_wheel - pip install -U setuptools wheel pip; pip install invoke
- set +x - invoke upload --pypi-user "$pypi_username" --pypi-password "$pypi_password"
- twine upload --non-interactive -u "$pypi_username" -p "$pypi_password" dist/*
environment: environment:
pypi_password: pypi_password:
from_secret: pypi_password from_secret: pypi_password
@ -105,6 +90,6 @@ depends_on:
--- ---
kind: signature kind: signature
hmac: dfd0429e3b9f364147c56a400cf37466d0cbf0966e613f11b726777553fd9931 hmac: 9a24ccae6182723af71257495d7843fd40874006c5e867cdebf363f497ddb839
... ...

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ aiohttp_pydantic.egg-info/
build/ build/
coverage.xml coverage.xml
dist/ dist/
dist_venv/
venv/

View File

@ -1,28 +1,42 @@
aiohttp==3.7.3
async-timeout==3.0.1 async-timeout==3.0.1
attrs==21.2.0 attrs==21.2.0
bleach==3.3.0 bleach==4.0.0
certifi==2021.5.30 certifi==2021.5.30
chardet==3.0.4 cffi==1.14.6
chardet==4.0.0
charset-normalizer==2.0.4
codecov==2.1.11 codecov==2.1.11
colorama==0.4.4
coverage==5.5 coverage==5.5
cryptography==3.4.7
docutils==0.17.1 docutils==0.17.1
idna==2.10 idna==3.2
importlib-metadata==4.6.3
iniconfig==1.1.1 iniconfig==1.1.1
jeepney==0.7.1
keyring==23.0.1
multidict==5.1.0 multidict==5.1.0
packaging==21.0 packaging==21.0
pkginfo==1.7.1
pluggy==0.13.1 pluggy==0.13.1
py==1.10.0 py==1.10.0
pycparser==2.20
Pygments==2.9.0 Pygments==2.9.0
pyparsing==2.4.7 pyparsing==2.4.7
pytest==6.1.2 pytest==6.1.2
pytest-aiohttp==0.3.0 pytest-aiohttp==0.3.0
pytest-cov==2.10.1 pytest-cov==2.10.1
readme-renderer==29.0 readme-renderer==29.0
requests==2.25.1 requests==2.26.0
requests-toolbelt==0.9.1
rfc3986==1.5.0
SecretStorage==3.3.1
six==1.16.0 six==1.16.0
toml==0.10.2 toml==0.10.2
tqdm==4.62.0
twine==3.4.2
typing-extensions==3.10.0.0 typing-extensions==3.10.0.0
urllib3==1.26.6 urllib3==1.26.6
webencodings==0.5.1 webencodings==0.5.1
yarl==1.6.3 yarl==1.6.3
zipp==3.5.0

View File

@ -1,14 +1,23 @@
attrs-21.2.0 async-timeout==3.0.1
coverage-5.5 attrs==21.2.0
bleach==4.0.0
chardet==4.0.0
coverage==5.5
docutils==0.17.1
idna==3.2
iniconfig==1.1.1 iniconfig==1.1.1
multidict==5.1.0
packaging==21.0 packaging==21.0
pluggy==0.13.1 pluggy==0.13.1
py==1.10.0 py==1.10.0
Pygments==2.9.0
pyparsing==2.4.7 pyparsing==2.4.7
pytest==6.2.4 pytest==6.1.2
pytest-aiohttp==0.3.0 pytest-aiohttp==0.3.0
pytest-cov-2.12.1 pytest-cov==2.10.1
pyyaml==5.3.1 readme-renderer==29.0
six==1.15.0 six==1.16.0
toml==0.10.2 toml==0.10.2
typing-extensions==3.7.4.3 typing-extensions==3.10.0.0
webencodings==0.5.1
yarl==1.6.3

View File

@ -35,13 +35,20 @@ install_requires =
swagger-ui-bundle swagger-ui-bundle
[options.extras_require] [options.extras_require]
test = pytest==6.1.2; pytest-aiohttp==0.3.0; pytest-cov==2.10.1 test =
ci = pytest==6.1.2; pytest-aiohttp==0.3.0; pytest-cov==2.10.1; codecov==2.1.11; readme-renderer==29.0 pytest==6.1.2
pytest-aiohttp==0.3.0
pytest-cov==2.10.1
readme-renderer==29.0
ci =
%(test)s
codecov==2.1.11
twine==3.4.2
[options.packages.find] [options.packages.find]
exclude = exclude =
tests tests*
demo demo*
[options.package_data] [options.package_data]
aiohttp_pydantic.oas = index.j2 aiohttp_pydantic.oas = index.j2

172
tasks.py Normal file
View File

@ -0,0 +1,172 @@
"""
To use this module, install invoke and type invoke -l
"""
from functools import partial
import os
from pathlib import Path
from setuptools.config import read_configuration
from invoke import task, Exit, Task as Task_, call
def activate_venv(c, venv: str):
"""
Activate a virtualenv
"""
virtual_env = Path().absolute() / venv
if original_path := os.environ.get("PATH"):
path = f'{virtual_env / "bin"}:{original_path}'
else:
path = str(virtual_env / "bin")
c.config.run.env["PATH"] = path
c.config.run.env["VIRTUAL_ENV"] = str(virtual_env)
os.environ.pop("PYTHONHOME", "")
def title(text, underline_char="#"):
"""
Display text as a title.
"""
template = f"{{:{underline_char}^80}}"
text = template.format(f" {text.strip()} ")
print(f"\033[1m{text}\033[0m")
class Task(Task_):
"""
This task add 'skip_if_recent' feature.
>>> @task(skip_if_recent=['./target', './dependency'])
>>> def my_tash(c):
>>> ...
target is file created by the task
dependency is file used by the task
The task is ran only if the dependency is more recent than the target file.
The target or the dependency can be a tuple of files.
"""
def __init__(self, *args, **kwargs):
self.skip_if_recent = kwargs.pop("skip_if_recent", None)
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
title(self.__doc__ or self.name)
if self.skip_if_recent:
targets, dependencies = self.skip_if_recent
if isinstance(targets, str):
targets = (targets,)
if isinstance(dependencies, str):
dependencies = (dependencies,)
target_mtime = min(
((Path(file).exists() and Path(file).lstat().st_mtime) or 0)
for file in targets
)
dependency_mtime = max(Path(file).lstat().st_mtime for file in dependencies)
if dependency_mtime < target_mtime:
print(f"{self.name}, nothing to do")
return None
return super().__call__(*args, **kwargs)
task = partial(task, klass=Task)
@task()
def venv(c):
"""
Create a virtual environment for dev
"""
c.run("python -m venv --clear venv")
c.run("venv/bin/pip install -U setuptools wheel pip")
c.run("venv/bin/pip install -e .")
c.run("venv/bin/pip install -r requirements/test.txt")
@task()
def check_readme(c):
"""
Check the README.rst render
"""
c.run("python -m readme_renderer -o /dev/null README.rst")
@task()
def test(c, isolate=False):
"""
Launch tests
"""
opt = "I" if isolate else ""
c.run(f"python -{opt}m pytest --cov-report=xml --cov=aiohttp_pydantic tests/")
@task()
def tag_eq_version(c):
"""
Ensure that the last git tag matches the package version
"""
git_tag = c.run("git describe --tags HEAD", hide=True).stdout.strip()
package_version = read_configuration("./setup.cfg")["metadata"]["version"]
if git_tag != f"v{package_version}":
raise Exit(
f"ERROR: The git tag {git_tag!r} does not matches"
f" the package version {package_version!r}"
)
@task()
def prepare_ci_env(c):
"""
Prepare CI environment
"""
title("Creating virtual env", "=")
c.run("python -m venv --clear dist_venv")
activate_venv(c, "dist_venv")
c.run("dist_venv/bin/python -m pip install -U setuptools wheel pip")
title("Building wheel", "=")
c.run("dist_venv/bin/python setup.py build bdist_wheel")
title("Installing wheel", "=")
package_version = read_configuration("./setup.cfg")["metadata"]["version"]
dist = next(Path("dist").glob(f"aiohttp_pydantic-{package_version}-*.whl"))
c.run(f"dist_venv/bin/python -m pip install {dist}")
# We verify that aiohttp-pydantic module is importable before installing CI tools.
package_names = read_configuration("./setup.cfg")["options"]["packages"]
for package_name in package_names:
c.run(f"dist_venv/bin/python -I -c 'import {package_name}'")
title("Installing CI tools", "=")
c.run("dist_venv/bin/python -m pip install -r requirements/ci.txt")
@task(prepare_ci_env, check_readme, call(test, isolate=True), klass=Task_)
def prepare_upload(c):
"""
Launch all tests and verifications
"""
@task(tag_eq_version, prepare_upload)
def upload(c, pypi_user=None, pypi_password=None):
"""
Upload on pypi
"""
package_version = read_configuration("./setup.cfg")["metadata"]["version"]
dist = next(Path("dist").glob(f"aiohttp_pydantic-{package_version}-*.whl"))
if pypi_user is not None and pypi_password is not None:
c.run(
f"dist_venv/bin/twine upload --non-interactive"
f" -u {pypi_user} -p {pypi_password} {dist}",
hide=True,
)
else:
c.run(f"dist_venv/bin/twine upload --repository aiohttp-pydantic {dist}")