From 47e91f99f6cab6ab0124f2a8fb1e97e272fb8595 Mon Sep 17 00:00:00 2001 From: Christian Bewernitz Date: Tue, 30 Apr 2024 08:29:00 +0200 Subject: [PATCH 1/5] feat: support all possible `elasticapm.*` settings - updated pre-commit regarding broken URLs - added bettermarks/.github git-hooks - run black on all code --- .pre-commit-config.yaml | 27 ++++++++++------- src/pyramid_elasticapm/__init__.py | 33 ++++++++++----------- src/pyramid_elasticapm/tests/test_plugin.py | 1 + 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 14fdd41..7181400 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,16 @@ -# See https://pre-commit.com for more information +# See https://github.com/bettermarks/.github#git-hooks for more information # See https://pre-commit.com/hooks.html for more hooks repos: - + - repo: https://github.com/bettermarks/.github + # to get the latest SHA use `gh api /repos/bettermarks/.github/commits/HEAD -q .sha` + rev: b8bcd419de6a66a6b3a5d7ca3197b801348690be + hooks: + - id: no-commit-to-default-branch + # the default branch of the repository containing this yaml file + args: [master] + - id: pre-commit-autoupdate - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.6.0 hooks: # These are the most critical checks. @@ -52,12 +59,12 @@ repos: # This checks that all links to our VCS are permalinks. How oddly specific! - id: check-vcs-permalinks - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + - repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 hooks: - id: flake8 - repo: https://github.com/asottile/pyupgrade - rev: v2.9.0 + rev: v3.15.2 hooks: - id: pyupgrade - repo: https://github.com/asottile/seed-isort-config @@ -65,14 +72,14 @@ repos: hooks: - id: seed-isort-config - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.7.0 # pick the isort version you'd like to use from https://github.com/pre-commit/mirrors-isort/releases + rev: v5.10.1 # pick the isort version you'd like to use from https://github.com/pre-commit/mirrors-isort/releases hooks: - id: isort - - repo: https://github.com/psf/black - rev: 20.8b1 + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.4.2 hooks: - id: black - repo: https://github.com/mgedmin/check-manifest - rev: '0.46' + rev: '0.49' hooks: - id: check-manifest diff --git a/src/pyramid_elasticapm/__init__.py b/src/pyramid_elasticapm/__init__.py index 55478e0..1afe722 100644 --- a/src/pyramid_elasticapm/__init__.py +++ b/src/pyramid_elasticapm/__init__.py @@ -4,9 +4,9 @@ import elasticapm import pkg_resources -from elasticapm.utils import compat, get_url_dict -from pyramid.events import ApplicationCreated, subscriber +from elasticapm.utils import get_url_dict from pyramid._compat import reraise +from pyramid.events import ApplicationCreated, subscriber def includeme(config): @@ -26,6 +26,10 @@ def elasticapm_instrument(event): elasticapm.instrument() +# https://www.elastic.co/guide/en/apm/agent/python/current/configuration.html +ELASTICAPM_PREFIX = 'elasticapm.' + + class TweenFactory: def __init__(self, handler, registry): self.handler = handler @@ -33,16 +37,10 @@ def __init__(self, handler, registry): settings = registry.settings config = { - 'SERVICE_NAME': settings['elasticapm.service_name'], - 'SERVER_URL': settings['elasticapm.server_url'], - 'SECRET_TOKEN': settings['elasticapm.secret_token'], - 'ENVIRONMENT': settings['elasticapm.environment'], - 'TRANSACTION_SAMPLE_RATE': settings['elasticapm.transaction_sample_rate'], + key.replace(ELASTICAPM_PREFIX, '').upper(): value + for key, value in settings.items() + if key.startswith(ELASTICAPM_PREFIX) } - if settings.get('elasticapm.transactions_ignore_patterns', ''): - config['TRANSACTIONS_IGNORE_PATTERNS'] = settings[ - 'elasticapm.transactions_ignore_patterns' - ].split() pkg_versions = dict() for pkg_name in ( @@ -68,7 +66,7 @@ def __init__(self, handler, registry): def __call__(self, request): self.client.begin_transaction('request') - transaction_result = "" + transaction_result = '' response = None try: response = self.handler(request) @@ -80,18 +78,17 @@ def __call__(self, request): except Exception: transaction_result = '5xx' self.client.capture_exception( - context={'request': self.get_data_from_request( - request, response - )}, + context={ + 'request': self.get_data_from_request(request, response) + }, handled=False, ) reraise(*sys.exc_info()) finally: transaction_name = self.get_transaction_name(request) elasticapm.set_context( - lambda: self.get_data_from_request( - request, response - ), 'request' + lambda: self.get_data_from_request(request, response), + 'request', ) elasticapm.set_user_context( user_id=request.authenticated_userid, diff --git a/src/pyramid_elasticapm/tests/test_plugin.py b/src/pyramid_elasticapm/tests/test_plugin.py index 8c9cbbc..fb91fca 100644 --- a/src/pyramid_elasticapm/tests/test_plugin.py +++ b/src/pyramid_elasticapm/tests/test_plugin.py @@ -13,6 +13,7 @@ def make_app(server_url): 'elasticapm.service_distribution': 'pytest', 'elasticapm.transactions_ignore_patterns': 'foo bar baz', 'elasticapm.transaction_sample_rate': 1.0, + 'elasticapm.collect_local_variables': 'off', } config = Configurator(settings=settings) config.include('pyramid_elasticapm') From 4d75baf60e0f11a0ad27fd299d68f8ec4d150ff5 Mon Sep 17 00:00:00 2001 From: Christian Bewernitz Date: Tue, 30 Apr 2024 09:08:59 +0200 Subject: [PATCH 2/5] chore: update the documentation and dependencies - update pyramid to 1.10 and emntion it in the readme - add contibuting section to readme - update install instructions - update repo URL in setup.py --- .gitignore | 1 + README.rst | 34 +++- pytest | 389 ---------------------------------------------- requirements.lock | 32 ++-- setup.py | 4 +- 5 files changed, 55 insertions(+), 405 deletions(-) delete mode 100755 pytest diff --git a/.gitignore b/.gitignore index c3a9395..bd2bfe7 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ dmypy.json # AppEnv .pytest/ +.envrc diff --git a/README.rst b/README.rst index 877ccf6..608539f 100644 --- a/README.rst +++ b/README.rst @@ -4,15 +4,22 @@ pyramid-elasticapm elastic-apm integration for the Pyramid framework -This package is inspired by https://www.elastic.co/de/blog/creating-custom-framework-integrations-with-the-elastic-apm-python-agent. +This package is inspired by https://www.elastic.co/de/blog/creating-custom-framework-integrations-with-the-elastic-apm-python-agent +**This fork is using pyramid 1.10 and python 3.8** +https://docs.pylonsproject.org/projects/pyramid/en/1.10-branch/ + +Since it is not published as a package, you have to install it from source, +e.g. by adding the following to requirements.in/requirements.txt:: + + pyramid_elasticapm @ git+https://github.com/betermarks/pyramid_elasticapm.git Installation ============ Install with pip:: - $ pip install pyramid_elasticapm + $ pip install git+https://github.com/betermarks/pyramid_elasticapm.git Then include it in your pyramid application via config:: @@ -31,7 +38,7 @@ Settings Settings for the elasticapm client can be specified via the `elasticapm` -namespace: +namespace, e.g.: * `elasticapm.server_url`: Specify the apm server url. * `elasticapm.secret_token`: Your secret authentication token for the server. @@ -43,3 +50,24 @@ namespace: * `elasticapm.transactions_ignore_patterns`: Whitespace separated list of ignore patterns. * `elasticapm.transaction_sample_rate`: Transaction sample rate +* `elasticapm.collect_local_variables`: Possible values: `errors` (default), `transactions`, `all`, `off` + Keep in mind that those things can contain data that is covered by GDPR policies + +All possible configuration options are supported, see +https://www.elastic.co/guide/en/apm/agent/python/current/configuration.html + +Contributing +============ + +1. install direnv: https://direnv.net/docs/installation.html +2. hook direnv: https://direnv.net/docs/hook.html +3. create a new file called `.envrc` with the following content:: + + # https://github.com/direnv/direnv/wiki/Python#venv-stdlib-module + export VIRTUAL_ENV=venv + layout python python3.8 +4. use `direnv allow` so direnv creates and activates the virtual env when entering the directory +5. use `pip install -r requirements.lock` to install the dependencies +6. run the tests `pytest` +7. use `pre-commit install` before your first commit, see https://github.com/bettermarks/.github#git-hooks +8. use `pip freeze --local > requirements.lock` to update the lock file, after the input changed diff --git a/pytest b/pytest deleted file mode 100755 index f6197e1..0000000 --- a/pytest +++ /dev/null @@ -1,389 +0,0 @@ -#!/usr/bin/env python3 -# appenv - a single file 'application in venv bootstrapping and updating -# mechanism for python-based (CLI) applications - -# Assumptions: -# -# - the appenv file is placed in a repo with the name of the application -# - the name of the application/file is an entrypoint XXX -# - python3.X+ with ensurepip -# - a requirements.txt file next to the appenv file - -# TODO -# -# - provide a `clone` meta command to create a new project based on this one -# maybe use an entry point to allow further initialisation of the clone. - - -import argparse -import glob -import hashlib -import http.client -import os -import os.path -import shutil -import subprocess -import sys -import tempfile -import venv - - -def cmd(c, merge_stderr=True, quiet=False): - # TODO revisit the cmd() architecture w/ python 3 - # XXX better IO management for interactive output and seeing original - # errors and output at appropriate places ... - try: - kwargs = {'shell': True} - if merge_stderr: - kwargs['stderr'] = subprocess.STDOUT - return subprocess.check_output([c], **kwargs) - except subprocess.CalledProcessError as e: - print('{} returned with exit code {}'.format(c, e.returncode)) - print(e.output.decode('ascii')) - raise ValueError(e.output.decode('ascii')) - - -def get(host, path, f): - conn = http.client.HTTPSConnection(host) - conn.request('GET', path) - r1 = conn.getresponse() - assert r1.status == 200, (r1.status, host, path, r1.read()[:100]) - chunk = r1.read(16 * 1024) - while chunk: - f.write(chunk) - chunk = r1.read(16 * 1024) - conn.close() - - -def ensure_venv(target): - if os.path.exists(os.path.join(target, 'bin', 'pip3')): - # XXX Support probing the target whether it works properly and rebuild - # if necessary - return - - if os.path.exists(target): - print('Deleting unclean target)') - cmd('rm -rf {target}'.format(target=target)) - - version = sys.version.split()[0] - python_maj_min = '.'.join(str(x) for x in sys.version_info[:2]) - print('Creating venv ...') - venv.create(target, with_pip=False) - - try: - # This is trying to detect whether we're on a proper Python stdlib - # or on a broken Debian. See various StackOverflow questions about - # this. - import distutils.util # noqa - import ensurepip # noqa - except ImportError: - # Okay, lets repair this, if we can. May need privilege escalation - # at some point. - # We could do: apt-get -y -q install python3-distutils python3-venv - # on some systems but it requires root and is specific to Debian. - # I decided to go a more sledge hammer route. - - # XXX we can speed this up by storing this in ~/.appenv/overlay instead - # of doing the download for every venv we manage - print('Activating broken distutils/ensurepip stdlib workaround ...') - - tmp_base = tempfile.mkdtemp() - try: - download = os.path.join(tmp_base, 'download.tar.gz') - with open(download, mode='wb') as f: - get( - 'www.python.org', - '/ftp/python/{v}/Python-{v}.tgz'.format(v=version), - f, - ) - - cmd('tar xf {} -C {}'.format(download, tmp_base)) - - assert os.path.exists( - os.path.join(tmp_base, 'Python-{}'.format(version)) - ) - for module in ['ensurepip', 'distutils']: - print(module) - shutil.copytree( - os.path.join( - tmp_base, 'Python-{}'.format(version), 'Lib', module - ), - os.path.join( - target, - 'lib', - 'python{}.{}'.format(*sys.version_info[:2]), - 'site-packages', - module, - ), - ) - - # (always) prepend the site packages so we can actually have a - # fixed distutils installation. - site_packages = os.path.abspath( - os.path.join( - target, 'lib', 'python' + python_maj_min, 'site-packages' - ) - ) - with open(os.path.join(site_packages, 'batou.pth'), 'w') as f: - f.write( - "import sys; sys.path.insert(0, '{}')\n".format( - site_packages - ) - ) - - finally: - shutil.rmtree(tmp_base) - - print('Ensuring pip ...') - cmd('{target}/bin/python -m ensurepip --default-pip'.format(target=target)) - if python_maj_min == '3.4': - # Last version of Pip supporting Python 3.4 - cmd( - '{target}/bin/python -m pip install --upgrade "pip<19.2"'.format( - target=target - ) - ) - else: - cmd( - '{target}/bin/python -m pip install --upgrade pip'.format( - target=target - ) - ) - - -def update_lockfile(argv, meta_args): - print('Updating lockfile') - tmpdir = os.path.join(meta_args.appenvdir, 'updatelock') - ensure_venv(tmpdir) - print('Installing packages ...') - cmd( - '{tmpdir}/bin/python -m pip install -r requirements.txt'.format( - tmpdir=tmpdir - ) - ) - result = cmd( - '{tmpdir}/bin/python -m pip freeze'.format(tmpdir=tmpdir), - merge_stderr=False, - ) - with open(os.path.join(meta_args.base, 'requirements.lock'), 'wb') as f: - f.write(result) - cmd('rm -rf {tmpdir}'.format(tmpdir=tmpdir)) - - -def _prepare(meta_args): - # copy used requirements.txt into the target directory so we can use that - # to check later - # - when to clean up old versions? keep like one or two old revisions? - # - enumerate the revisions and just copy the requirements.txt, check - # for ones that are clean or rebuild if necessary - if meta_args.unclean or not os.path.exists('requirements.lock'): - print('Running unclean installation from requirements.txt') - env_dir = os.path.join(meta_args.appenvdir, 'unclean') - ensure_venv(env_dir) - print('Ensuring unclean install ...') - cmd( - '{env_dir}/bin/python -m pip install -r requirements.txt ' - '--upgrade'.format(env_dir=env_dir) - ) - else: - hash_content = [] - requirements = open('requirements.lock', 'rb').read() - hash_content.append(os.fsencode(os.path.realpath(sys.executable))) - hash_content.append(requirements) - hash_content.append(open(__file__, 'rb').read()) - env_hash = hashlib.new('sha256', b''.join(hash_content)).hexdigest()[ - :8 - ] - env_dir = os.path.join(meta_args.appenvdir, env_hash) - - whitelist = {env_dir, os.path.join(meta_args.appenvdir, 'unclean')} - for path in glob.glob( - '{meta_args.appenvdir}/*'.format(meta_args=meta_args) - ): - if path not in whitelist: - print('Removing expired path: {path} ...'.format(path=path)) - if not os.path.isdir(path): - os.unlink(path) - else: - shutil.rmtree(path) - if os.path.exists(env_dir): - # check whether the existing environment is OK, it might be nice - # to rebuild in a separate place if necessary to avoid - # interruptions to running services, but that isn't what we're - # using it for at the moment - try: - if not os.path.exists( - '{env_dir}/appenv.ready'.format(env_dir=env_dir) - ): - raise Exception() - except Exception: - print('Existing envdir not consistent, deleting') - cmd('rm -rf {env_dir}'.format(env_dir=env_dir)) - - if not os.path.exists(env_dir): - ensure_venv(env_dir) - - with open(os.path.join(env_dir, 'requirements.lock'), 'wb') as f: - f.write(requirements) - - print( - 'Installing {meta_args.appname} ...'.format( - meta_args=meta_args - ) - ) - cmd( - '{env_dir}/bin/python -m pip install --no-deps -r ' - '{env_dir}/requirements.lock'.format(env_dir=env_dir) - ) - - cmd('{env_dir}/bin/python -m pip check'.format(env_dir=env_dir)) - - with open(os.path.join(env_dir, 'appenv.ready'), 'w') as f: - f.write("Ready or not, here I come, you can't hide\n") - - return env_dir - - -def run_app(argv, meta_args): - os.chdir(meta_args.base) - env_dir = _prepare(meta_args) - # Allow called programs to find out where the wrapper lives - os.environ['APPENV_BASEDIR'] = meta_args.base - os.execv(os.path.join(env_dir, 'bin', meta_args.appname), argv) - - -def python(argv, meta_args): - meta_args.command = 'python' - run(argv, meta_args) - - -def run(argv, meta_args): - os.chdir(meta_args.base) - env_dir = _prepare(meta_args) - cmd = os.path.join(env_dir, 'bin', meta_args.command) - argv[0] = cmd - os.environ['APPENV_BASEDIR'] = meta_args.base - os.execv(cmd, argv) - - -def reset(argv, meta_args): - print( - 'Resetting ALL application environments in {appenvdir} ...'.format( - appenvdir=meta_args.appenvdir - ) - ) - cmd('rm -rf {appenvdir}'.format(appenvdir=meta_args.appenvdir)) - - -def init(argv, meta_args): - print("Let's create a new appenv project.\n") - command = None - while not command: - command = input('What should the command be named? ').strip() - dependency = input( - 'What is the main dependency as found on PyPI? [{}] '.format(command) - ).strip() - if not dependency: - dependency = command - workdir = os.getcwd() - default_target = os.path.join(workdir, command) - target = input( - 'Where should we create this? [{}] '.format(default_target) - ).strip() - if target: - target = os.path.join(workdir, target) - else: - target = default_target - target = os.path.abspath(target) - if not os.path.exists(target): - os.makedirs(target) - print() - print('Creating appenv setup in {} ...'.format(target)) - with open(__file__, 'rb') as bootstrap_file: - bootstrap_data = bootstrap_file.read() - os.chdir(target) - with open(command, 'wb') as new_command_file: - new_command_file.write(bootstrap_data) - os.chmod(command, 0o755) - with open('requirements.txt', 'w') as requirements_txt: - requirements_txt.write(dependency + '\n') - print() - print( - 'Done. You can now `cd {}` and call `./{}` to bootstrap and run ' - 'it.'.format(os.path.relpath(target, workdir), command) - ) - - -def main(): - # clear PYTHONPATH variable to get a defined environment - # XXX this is a bit of history. not sure whether its still needed. - # keeping it for good measure - if 'PYTHONPATH' in os.environ: - del os.environ['PYTHONPATH'] - - # Prepare args for us and args for the actual target program. - meta_argv = [] - argv = [] - - # Preprocess sys.arv - sys_argv = list(sys.argv) - while sys_argv: - arg = sys_argv.pop(0) - if 'appenv-' in arg: - meta_arg = arg.replace('appenv-', '') - meta_argv.append(meta_arg) - if meta_arg == 'run': - # Hack, hack. - meta_argv.append(sys_argv.pop(0)) - else: - argv.append(arg) - - default_appname = os.path.splitext(os.path.basename(__file__))[0] - - # Parse the appenv arguments - meta_parser = argparse.ArgumentParser() - meta_parser.add_argument( - '-u', - '--unclean', - action='store_true', - help='Use an unclean working environment.', - ) - - meta_parser.add_argument('--appname', default=default_appname) - meta_parser.add_argument('--appenvdir', default='.' + default_appname) - meta_parser.set_defaults(func=run_app) - meta_parser.add_argument( - '--base', default=os.path.abspath(os.path.dirname(__file__)) - ) - - subparsers = meta_parser.add_subparsers() - p = subparsers.add_parser('update-lockfile', help='Update the lock file.') - p.set_defaults(func=update_lockfile) - - p = subparsers.add_parser('init', help='Create a new appenv project.') - p.set_defaults(func=init) - - p = subparsers.add_parser('reset', help='Reset the environment.') - p.set_defaults(func=reset) - - p = subparsers.add_parser( - 'python', help='Spawn the embedded Python interpreter REPL' - ) - p.set_defaults(func=python) - - p = subparsers.add_parser( - 'run', help="Run a command from the venv's bin/ directory" - ) - p.add_argument('command') - p.set_defaults(func=run) - - meta_args = meta_parser.parse_args(meta_argv) - - if not os.path.exists(meta_args.appenvdir): - os.makedirs(meta_args.appenvdir) - - meta_args.func(argv, meta_args) - - -if __name__ == '__main__': - main() diff --git a/requirements.lock b/requirements.lock index df2824c..0e319a3 100644 --- a/requirements.lock +++ b/requirements.lock @@ -1,29 +1,34 @@ --e . -PasteDeploy==2.1.1 -WebOb==1.8.7 -WebTest==2.0.35 -Werkzeug==1.0.1 apipkg==1.5 attrs==20.3.0 beautifulsoup4==4.9.3 +black==24.4.2 certifi==2020.12.5 +click==8.1.7 coverage==5.5 -elastic-apm==6.1.2 +ecs-logging==2.1.0 +elastic-apm==6.22.0 +exceptiongroup==1.2.1 execnet==1.8.0 flake8==3.9.1 hupper==1.10.2 importlib-metadata==4.0.0 iniconfig==1.1.1 +MarkupSafe==2.1.5 mccabe==0.6.1 -packaging==20.9 -plaster-pastedeploy==0.7 +mypy-extensions==1.0.0 +packaging==24.0 +PasteDeploy==2.1.1 +pathspec==0.12.1 plaster==1.0 +plaster-pastedeploy==0.7 +platformdirs==4.2.1 pluggy==0.13.1 py==1.10.0 pycodestyle==2.7.0 pyflakes==2.3.1 pyparsing==2.4.7 -pyramid==1.8.0 +pyramid==1.10.8 +pytest==6.2.3 pytest-cache==1.0 pytest-cov==2.11.1 pytest-flake8==1.0.7 @@ -32,16 +37,21 @@ pytest-localserver==0.5.0 pytest-rerunfailures==9.1.1 pytest-sugar==0.9.4 pytest-xdist==2.2.1 -pytest==6.2.3 +repoze.lru==0.7 six==1.15.0 soupsieve==2.2.1 termcolor==1.1.0 toml==0.10.2 +tomli==2.0.1 translationstring==1.4 -typing-extensions==3.7.4.3 +typing_extensions==4.11.0 urllib3==1.26.4 venusian==3.0.0 waitress==2.0.0 +WebOb==1.8.7 +WebTest==2.0.35 +Werkzeug==1.0.1 +wrapt==1.14.1 zipp==3.4.1 zope.deprecation==4.4.0 zope.interface==5.4.0 diff --git a/setup.py b/setup.py index 38ffaa1..2d1671c 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ version='1.0.5.dev0', install_requires=[ 'elastic-apm', - 'pyramid', + 'pyramid==1.10.8', ], extras_require={ 'test': [ @@ -29,7 +29,7 @@ author='Sebastian Wehrmann (riscLOG Solution GmbH)', author_email='sebastian@risclog.com', license='BSD', - url='https://github.com/risclog-solution/pyramid-elasticapm', + url='https://github.com/bettermarks/pyramid-elasticapm', keywords='elastic apm pyramid', classifiers="""\ License :: OSI Approved :: BSD License From bbd1a655db99007937ddafec8b9d84050ac5ff43 Mon Sep 17 00:00:00 2001 From: Christian Bewernitz Date: Tue, 30 Apr 2024 09:10:16 +0200 Subject: [PATCH 3/5] ci: drop scheduled trigger and update actions --- .github/workflows/test.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a52f891..6c276b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,9 +5,6 @@ on: branches: - master pull_request: - schedule: - # * is a special character in YAML so you have to quote this string - - cron: '0 3 * * *' jobs: @@ -15,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python 3.9 - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.8 - name: Test with pytest if: always() run: cd $GITHUB_WORKSPACE && ./pytest From 228774a4ff02f694975b0eb6f46d769a984900ac Mon Sep 17 00:00:00 2001 From: Christian Bewernitz Date: Mon, 13 May 2024 09:12:01 +0200 Subject: [PATCH 4/5] ci: install deps and use pytest from path --- .github/workflows/test.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c276b5..35c23cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,14 +11,12 @@ jobs: test: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Python 3.9 - uses: actions/setup-python@v5 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: 3.8 - - name: Test with pytest - if: always() - run: cd $GITHUB_WORKSPACE && ./pytest + - run: pip install -U pip + - run: pip install -r requirements.lock + - run: pytest env: PYTHONUNBUFFERED: 1 From d752c06da4e0fc10f53776f7471b55838a356297 Mon Sep 17 00:00:00 2001 From: Christian Bewernitz Date: Mon, 13 May 2024 09:21:30 +0200 Subject: [PATCH 5/5] docs: running tests requires an editable install since it requires access to the distribution --- .github/workflows/test.yml | 2 +- README.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 35c23cb..521b30c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: with: python-version: 3.8 - run: pip install -U pip - - run: pip install -r requirements.lock + - run: pip install -e . -r requirements.lock - run: pytest env: PYTHONUNBUFFERED: 1 diff --git a/README.rst b/README.rst index 608539f..955fcb0 100644 --- a/README.rst +++ b/README.rst @@ -67,7 +67,7 @@ Contributing export VIRTUAL_ENV=venv layout python python3.8 4. use `direnv allow` so direnv creates and activates the virtual env when entering the directory -5. use `pip install -r requirements.lock` to install the dependencies +5. use `pip install -e . -r requirements.lock` to install the dependencies 6. run the tests `pytest` 7. use `pre-commit install` before your first commit, see https://github.com/bettermarks/.github#git-hooks 8. use `pip freeze --local > requirements.lock` to update the lock file, after the input changed