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",
image: image,
commands: [
"echo Install package",
"pip install -U setuptools wheel pip; pip install .",
"echo Test to import module of package",
"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"
"test \"$(md5sum tasks.py)\" = \"18f864b3ac76119938e3317e49b4ffa1 tasks.py\"",
"pip install -U setuptools wheel pip; pip install invoke",
"invoke prepare-upload"
]
},
{
@ -56,7 +49,7 @@ local BuildAndTestPipeline(name, image) = {
steps: [
{
name: "Install twine and deploy",
image: "python3.8",
image: "python:3.8",
environment: {
pypi_username: {
from_secret: 'pypi_username'
@ -66,10 +59,9 @@ local BuildAndTestPipeline(name, image) = {
}
},
commands: [
"pip install --force-reinstall twine wheel",
"python setup.py build bdist_wheel",
"set +x",
"twine upload --non-interactive -u \"$pypi_username\" -p \"$pypi_password\" dist/*"
"test \"$(md5sum tasks.py)\" = \"18f864b3ac76119938e3317e49b4ffa1 tasks.py\"",
"pip install -U setuptools wheel pip; pip install invoke",
"invoke upload --pypi-user \"$pypi_username\" --pypi-password \"$pypi_password\""
]
},
],

View File

@ -11,16 +11,9 @@ steps:
- name: Install package and test
image: python:3.8
commands:
- echo Install package
- pip install -U setuptools wheel pip; pip install .
- echo Test to import module of package
- 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
- test "$(md5sum tasks.py)" = "18f864b3ac76119938e3317e49b4ffa1 tasks.py"
- pip install -U setuptools wheel pip; pip install invoke
- invoke prepare-upload
- name: coverage
image: plugins/codecov
@ -48,16 +41,9 @@ steps:
- name: Install package and test
image: python:3.9
commands:
- echo Install package
- pip install -U setuptools wheel pip; pip install .
- echo Test to import module of package
- 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
- test "$(md5sum tasks.py)" = "18f864b3ac76119938e3317e49b4ffa1 tasks.py"
- pip install -U setuptools wheel pip; pip install invoke
- invoke prepare-upload
- name: coverage
image: plugins/codecov
@ -83,12 +69,11 @@ platform:
steps:
- name: Install twine and deploy
image: python3.8
image: python:3.8
commands:
- pip install --force-reinstall twine wheel
- python setup.py build bdist_wheel
- set +x
- twine upload --non-interactive -u "$pypi_username" -p "$pypi_password" dist/*
- test "$(md5sum tasks.py)" = "18f864b3ac76119938e3317e49b4ffa1 tasks.py"
- pip install -U setuptools wheel pip; pip install invoke
- invoke upload --pypi-user "$pypi_username" --pypi-password "$pypi_password"
environment:
pypi_password:
from_secret: pypi_password
@ -105,6 +90,6 @@ depends_on:
---
kind: signature
hmac: dfd0429e3b9f364147c56a400cf37466d0cbf0966e613f11b726777553fd9931
hmac: 9a24ccae6182723af71257495d7843fd40874006c5e867cdebf363f497ddb839
...

2
.gitignore vendored
View File

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

View File

@ -1,28 +1,42 @@
aiohttp==3.7.3
async-timeout==3.0.1
attrs==21.2.0
bleach==3.3.0
bleach==4.0.0
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
colorama==0.4.4
coverage==5.5
cryptography==3.4.7
docutils==0.17.1
idna==2.10
idna==3.2
importlib-metadata==4.6.3
iniconfig==1.1.1
jeepney==0.7.1
keyring==23.0.1
multidict==5.1.0
packaging==21.0
pkginfo==1.7.1
pluggy==0.13.1
py==1.10.0
pycparser==2.20
Pygments==2.9.0
pyparsing==2.4.7
pytest==6.1.2
pytest-aiohttp==0.3.0
pytest-cov==2.10.1
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
toml==0.10.2
tqdm==4.62.0
twine==3.4.2
typing-extensions==3.10.0.0
urllib3==1.26.6
webencodings==0.5.1
yarl==1.6.3
zipp==3.5.0

View File

@ -1,14 +1,23 @@
attrs-21.2.0
coverage-5.5
async-timeout==3.0.1
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
multidict==5.1.0
packaging==21.0
pluggy==0.13.1
py==1.10.0
Pygments==2.9.0
pyparsing==2.4.7
pytest==6.2.4
pytest==6.1.2
pytest-aiohttp==0.3.0
pytest-cov-2.12.1
pyyaml==5.3.1
six==1.15.0
pytest-cov==2.10.1
readme-renderer==29.0
six==1.16.0
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
[options.extras_require]
test = pytest==6.1.2; pytest-aiohttp==0.3.0; pytest-cov==2.10.1
ci = pytest==6.1.2; pytest-aiohttp==0.3.0; pytest-cov==2.10.1; codecov==2.1.11; readme-renderer==29.0
test =
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]
exclude =
tests
demo
tests*
demo*
[options.package_data]
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}")