From 07272eafbd06fe325ecdfd4b9db76efc210fe96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiziano=20M=C3=BCller?= Date: Wed, 27 Sep 2023 17:08:58 +0000 Subject: [PATCH] feature/pre commit updates (#102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix pre-commit config and typings * tests: move registry infos to fixtures Signed-off-by: Tiziano Müller --- .pre-commit-config.yaml | 41 ++++++++++---------------- CHANGELOG.md | 1 + oras/logger.py | 2 +- oras/oci.py | 2 +- oras/provider.py | 8 ++--- oras/tests/conftest.py | 58 +++++++++++++++++++++++++++++++++++++ oras/tests/test_oras.py | 47 +++++++++--------------------- oras/tests/test_provider.py | 31 +++----------------- oras/utils/fileio.py | 2 +- oras/version.py | 11 ++----- pyproject.toml | 3 ++ 11 files changed, 104 insertions(+), 102 deletions(-) create mode 100644 oras/tests/conftest.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d2c686..0ce2acd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ".all-contributorsrc|.tributors" repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -9,30 +9,21 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - id: mixed-line-ending - - - repo: local + - repo: https://github.com/pycqa/isort + rev: 5.12.0 hooks: - - id: black - name: black - language: python - types: [python] - entry: black - - id: isort - name: isort - args: [--filter-files] - language: python - types: [python] - entry: isort - - - id: mypy - name: mypy - language: python - types: [python] - entry: mypy - + - repo: https://github.com/psf/black + rev: 23.9.1 + hooks: + - id: black + language_version: python3.11 + - repo: https://github.com/pycqa/flake8 + rev: 6.1.0 + hooks: - id: flake8 - name: flake8 - language: python - types: [python] - entry: flake8 + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.5.1 + hooks: + - id: mypy + additional_dependencies: ["types-requests"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cf9869..cfefbec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and **Merged pull requests**. Critical items to know are: The versions coincide with releases on pip. Only major versions will be released as tags on Github. ## [0.0.x](https://github.com/oras-project/oras-py/tree/main) (0.0.x) + - refactor tests using fixtures and rework pre-commit configuration (0.1.25) - eliminate the additional subdirectory creation while pulling an image to a custom output directory (0.1.24) - updating the exclude string in the pyproject.toml file to match the [data type black expects](https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-format) - patch fix for pulling artifacts by digest (0.1.23) diff --git a/oras/logger.py b/oras/logger.py index e293c9f..2b3c992 100644 --- a/oras/logger.py +++ b/oras/logger.py @@ -213,7 +213,7 @@ def exit(self, msg: str, return_code: int = 1): self.handler({"level": "error", "msg": msg}) sys.exit(return_code) - def progress(self, done: int = None, total: int = None): + def progress(self, done: int, total: int): """ Show piece of a progress bar diff --git a/oras/oci.py b/oras/oci.py index 5bb3836..8c37087 100644 --- a/oras/oci.py +++ b/oras/oci.py @@ -116,7 +116,7 @@ def NewLayer( def ManifestConfig( - path: str = None, media_type: str = None + path: Optional[str] = None, media_type: Optional[str] = None ) -> Tuple[Dict[str, object], str]: """ Write an empty config, if one is not provided diff --git a/oras/provider.py b/oras/provider.py index 1a46442..b03e197 100644 --- a/oras/provider.py +++ b/oras/provider.py @@ -816,7 +816,7 @@ def pull(self, *args, **kwargs) -> List[str]: @decorator.ensure_container def get_manifest( - self, container: container_type, allowed_media_type: list = None + self, container: container_type, allowed_media_type: Optional[list] = None ) -> dict: """ Retrieve a manifest for a package. @@ -842,9 +842,9 @@ def do_request( self, url: str, method: str = "GET", - data: Union[dict, bytes] = None, - headers: dict = None, - json: dict = None, + data: Optional[Union[dict, bytes]] = None, + headers: Optional[dict] = None, + json: Optional[dict] = None, stream: bool = False, ): """ diff --git a/oras/tests/conftest.py b/oras/tests/conftest.py new file mode 100644 index 0000000..7b038dc --- /dev/null +++ b/oras/tests/conftest.py @@ -0,0 +1,58 @@ +import os +from dataclasses import dataclass + +import pytest + + +@dataclass +class TestCredentials: + with_auth: bool + user: str + password: str + + +@pytest.fixture +def registry(): + host = os.environ.get("ORAS_HOST") + port = os.environ.get("ORAS_PORT") + + if not host or not port: + pytest.skip( + "You must export ORAS_HOST and ORAS_PORT" + " for a running registry before running tests." + ) + + return f"{host}:{port}" + + +@pytest.fixture +def credentials(request): + with_auth = os.environ.get("ORAS_AUTH") == "true" + user = os.environ.get("ORAS_USER", "myuser") + pwd = os.environ.get("ORAS_PASS", "mypass") + + if with_auth and not user or not pwd: + pytest.skip("To test auth you need to export ORAS_USER and ORAS_PASS") + + marks = [m.name for m in request.node.iter_markers()] + if request.node.parent: + marks += [m.name for m in request.node.parent.iter_markers()] + + if request.node.get_closest_marker("with_auth"): + if request.node.get_closest_marker("with_auth").args[0] != with_auth: + if with_auth: + pytest.skip("test requires un-authenticated access to registry") + else: + pytest.skip("test requires authenticated access to registry") + + return TestCredentials(with_auth, user, pwd) + + +@pytest.fixture +def target(registry): + return f"{registry}/dinosaur/artifact:v1" + + +@pytest.fixture +def target_dir(registry): + return f"{registry}/dinosaur/directory:v1" diff --git a/oras/tests/test_oras.py b/oras/tests/test_oras.py index 0f9bf73..06df44f 100644 --- a/oras/tests/test_oras.py +++ b/oras/tests/test_oras.py @@ -4,7 +4,6 @@ import os import shutil -import sys import pytest @@ -12,31 +11,8 @@ here = os.path.abspath(os.path.dirname(__file__)) -registry_host = os.environ.get("ORAS_HOST") -registry_port = os.environ.get("ORAS_PORT") -with_auth = os.environ.get("ORAS_AUTH") == "true" -oras_user = os.environ.get("ORAS_USER", "myuser") -oras_pass = os.environ.get("ORAS_PASS", "mypass") - -def setup_module(module): - """ - Ensure the registry port and host is in the environment. - """ - if not registry_host or not registry_port: - sys.exit( - "You must export ORAS_HOST and ORAS_PORT for a running registry before running tests." - ) - if with_auth and not oras_user or not oras_pass: - sys.exit("To test auth you need to export ORAS_USER and ORAS_PASS") - - -registry = f"{registry_host}:{registry_port}" -target = f"{registry}/dinosaur/artifact:v1" -target_dir = f"{registry}/dinosaur/directory:v1" - - -def test_basic_oras(): +def test_basic_oras(registry): """ Basic tests for oras (without authentication) """ @@ -44,21 +20,24 @@ def test_basic_oras(): assert "Python version" in client.version() -@pytest.mark.skipif(not with_auth, reason="basic auth is needed for login/logout") -def test_login_logout(): +@pytest.mark.with_auth(True) +def test_login_logout(registry, credentials): """ Login and logout are all we can test with basic auth! """ client = oras.client.OrasClient(hostname=registry, insecure=True) res = client.login( - hostname=registry, username=oras_user, password=oras_pass, insecure=True + hostname=registry, + username=credentials.user, + password=credentials.password, + insecure=True, ) assert res["Status"] == "Login Succeeded" client.logout(registry) -@pytest.mark.skipif(with_auth, reason="token auth is needed for push and pull") -def test_basic_push_pull(tmp_path): +@pytest.mark.with_auth(False) +def test_basic_push_pull(tmp_path, registry, credentials, target): """ Basic tests for oras (without authentication) """ @@ -88,8 +67,8 @@ def test_basic_push_pull(tmp_path): assert res.status_code == 201 -@pytest.mark.skipif(with_auth, reason="token auth is needed for push and pull") -def test_get_delete_tags(tmp_path): +@pytest.mark.with_auth(False) +def test_get_delete_tags(tmp_path, registry, credentials, target): """ Test creationg, getting, and deleting tags. """ @@ -139,8 +118,8 @@ def test_get_many_tags(): assert len(tags) == 10 -@pytest.mark.skipif(with_auth, reason="token auth is needed for push and pull") -def test_directory_push_pull(tmp_path): +@pytest.mark.with_auth(False) +def test_directory_push_pull(tmp_path, registry, credentials, target_dir): """ Test push and pull for directory """ diff --git a/oras/tests/test_provider.py b/oras/tests/test_provider.py index 9015524..d3b014b 100644 --- a/oras/tests/test_provider.py +++ b/oras/tests/test_provider.py @@ -3,7 +3,6 @@ __license__ = "Apache-2.0" import os -import sys from pathlib import Path import pytest @@ -15,35 +14,13 @@ here = os.path.abspath(os.path.dirname(__file__)) -registry_host = os.environ.get("ORAS_HOST") -registry_port = os.environ.get("ORAS_PORT") -with_auth = os.environ.get("ORAS_AUTH") == "true" -oras_user = os.environ.get("ORAS_USER", "myuser") -oras_pass = os.environ.get("ORAS_PASS", "mypass") - -def setup_module(module): - """ - Ensure the registry port and host is in the environment. - """ - if not registry_host or not registry_port: - sys.exit( - "You must export ORAS_HOST and ORAS_PORT for a running registry before running tests." - ) - if with_auth and not oras_user or not oras_pass: - sys.exit("To test auth you need to export ORAS_USER and ORAS_PASS") - - -registry = f"{registry_host}:{registry_port}" -target = f"{registry}/dinosaur/artifact:v1" -target_dir = f"{registry}/dinosaur/directory:v1" - - -@pytest.mark.skipif(with_auth, reason="token auth is needed for push and pull") -def test_annotated_registry_push(tmp_path): +@pytest.mark.with_auth(False) +def test_annotated_registry_push(tmp_path, registry, credentials, target): """ Basic tests for oras push with annotations """ + # Direct access to registry functions remote = oras.provider.Registry(hostname=registry, insecure=True) client = oras.client.OrasClient(hostname=registry, insecure=True) @@ -84,7 +61,7 @@ def test_annotated_registry_push(tmp_path): ) -def test_parse_manifest(): +def test_parse_manifest(registry): """ Test parse manifest function. diff --git a/oras/utils/fileio.py b/oras/utils/fileio.py index 5019df3..db30820 100644 --- a/oras/utils/fileio.py +++ b/oras/utils/fileio.py @@ -189,7 +189,7 @@ def get_tmpdir( return tmpdir -def recursive_find(base: str, pattern: str = None) -> Generator: +def recursive_find(base: str, pattern: Optional[str] = None) -> Generator: """ Find filenames that match a particular pattern, and yield them. diff --git a/oras/version.py b/oras/version.py index e6cf92f..7db5671 100644 --- a/oras/version.py +++ b/oras/version.py @@ -2,7 +2,7 @@ __copyright__ = "Copyright The ORAS Authors." __license__ = "Apache-2.0" -__version__ = "0.1.24" +__version__ = "0.1.25" AUTHOR = "Vanessa Sochat" EMAIL = "vsoch@users.noreply.github.com" NAME = "oras" @@ -19,14 +19,7 @@ ("requests", {"min_version": None}), ) -TESTS_REQUIRES = ( - ("pytest", {"min_version": "4.6.2"}), - ("mypy", {"min_version": None}), - ("pyflakes", {"min_version": None}), - ("black", {"min_version": None}), - ("types-requests", {"min_version": None}), - ("isort", {"min_version": None}), -) +TESTS_REQUIRES = (("pytest", {"min_version": "4.6.2"}),) DOCKER_REQUIRES = (("docker", {"exact_version": "5.0.1"}),) diff --git a/pyproject.toml b/pyproject.toml index fedae4b..8fa6a18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,3 +8,6 @@ skip = [] [tool.mypy] mypy_path = ["oras", "examples"] + +[tool.pytest.ini_options] +markers = ["with_auth: mark for tests requiring authenticated registry access (or not)"]