diff --git a/.config/dictionary.txt b/.config/dictionary.txt index e822547..ff6c068 100644 --- a/.config/dictionary.txt +++ b/.config/dictionary.txt @@ -1 +1,7 @@ -pipc +bindep +bthornto +fileh +levelname +levelno +pip4a +uninstallation diff --git a/.config/requirements-test.txt b/.config/requirements-test.txt new file mode 100644 index 0000000..d6333f2 --- /dev/null +++ b/.config/requirements-test.txt @@ -0,0 +1,4 @@ +pytest +coverage[toml] +pytest-xdist +tox diff --git a/.config/requirements.in b/.config/requirements.in index e69de29..c3726e8 100644 --- a/.config/requirements.in +++ b/.config/requirements.in @@ -0,0 +1 @@ +pyyaml diff --git a/.gitignore b/.gitignore index 7b0600e..c8e20ed 100644 --- a/.gitignore +++ b/.gitignore @@ -166,4 +166,4 @@ cython_debug/ # and should all have detailed explanations # Version created and populated by setuptools_scm -/src/ansible_cdk/_version.py +/src/pipc/_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 79c4a33..6f69b20 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -78,6 +78,7 @@ repos: additional_dependencies: - pytest - tox + - pyyaml - repo: https://github.com/pre-commit/mirrors-mypy.git rev: v1.5.0 diff --git a/README.md b/README.md index 0e80c85..2db5b0b 100644 --- a/README.md +++ b/README.md @@ -1 +1,103 @@ -# pipc +# pip4a + +A pip-like install for ansible collections. + +## Features + +- Promotes an "ephemeral" development approach +- Ensures the current development environment is isolated +- Install all collection python requirements +- Install all collection test requirements +- Checks for missing system packages +- Symlinks the current collection into the current python interpreter's site-packages +- Install all collection collection dependencies into the current python interpreter's site-packages + +By placing collections into the python site-packages directory they are discoverable by ansible as well as python and pytest. + +## Usage + +### Setting up a development environment + +``` +$ git clone +$ cd collection_repo +$ python -m venv venv +$ source venv/bin/activate +$ pip install pip4a +$ pip4a install -e .[test] +INFO Found collection name: ansible.scm from /home/bthornto/github/ansible.scm/galaxy.yml. +INFO Requirements file /home/bthornto/github/ansible.scm/requirements.txt is empty, skipping +INFO Installing python requirements from /home/bthornto/github/ansible.scm/test-requirements.txt +INFO Initializing build directory: /home/bthornto/github/ansible.scm/build +INFO Running ansible-galaxy to build collection. +INFO Running ansible-galaxy to install collection and it's dependencies. +INFO Removing installed /home/bthornto/github/ansible.scm/venv/lib64/python3.11/site-packages/ansible_collections/ansible/scm +INFO Symlinking /home/bthornto/github/ansible.scm/venv/lib64/python3.11/site-packages/ansible_collections/ansible/scm to /home/bthornto/github/ansible.scm +``` + +### Tearing down the development environment + +``` +$ pip4a uninstall ansible.scm +INFO Found collection name: ansible.scm from /home/bthornto/github/ansible.scm/galaxy.yml. +INFO Requirements file /home/bthornto/github/ansible.scm/requirements.txt is empty, skipping +INFO Uninstalling python requirements from /home/bthornto/github/ansible.scm/test-requirements.txt +INFO Removed ansible.utils: /home/bthornto/github/ansible.scm/venv/lib64/python3.11/site-packages/ansible_collections/ansible/utils +INFO Removed ansible.utils*.info: /home/bthornto/github/ansible.scm/venv/lib64/python3.11/site-packages/ansible_collections/ansible.utils-2.10.3.info +INFO Removed ansible.scm: /home/bthornto/github/ansible.scm/venv/lib64/python3.11/site-packages/ansible_collections/ansible/scm +INFO Removed collection namespace root: /home/bthornto/github/ansible.scm/venv/lib64/python3.11/site-packages/ansible_collections/ansible +INFO Removed collection root: /home/bthornto/github/ansible.scm/venv/lib64/python3.11/site-packages/ansible_collections +``` + +## Help + +``` +$ pip4a --help +usage: pip4a [-h] [--verbose] {install,uninstall} ... + +A pip-like ansible collection installer. + +options: + -h, --help show this help message and exit + --verbose Increase output verbosity. + +subcommands: + valid subcommands + + {install,uninstall} additional help +``` + +``` +$ pip4a install --help +usage: pip4a install [-h] [-e] collection_specifier + +positional arguments: + collection_specifier Collection to install. + +options: + -h, --help show this help message and exit + -e, --editable Install editable. + +Usage: + pip4a install . + pip4a install -e . + pip4a install -e .[test] + python -m pip4a install ansible.utils +``` + +```` +$ pip4a uninstall --help +usage: pip4a uninstall [-h] collection_specifier + +positional arguments: + collection_specifier Collection to uninstall. + +options: + -h, --help show this help message and exit + +Usage: + pip4a install . + pip4a install -e . + pip4a install -e .[test] + python -m pip4a install ansible.utils``` +```` diff --git a/pyproject.toml b/pyproject.toml index a3ce7d0..6db4ac4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,18 +24,18 @@ dynamic = ["dependencies", "optional-dependencies", "version"] keywords = ["ansible"] license = {text = "GPL-3.0-only"} maintainers = [{"email" = "info@ansible.com", "name" = "Ansible by Red Hat"}] -name = "pipc" +name = "pipac" readme = "README.md" requires-python = ">=3.9" [project.scripts] -pipc = "pipc.cli:main" +pip4a = "pip4a.cli:main" [project.urls] -changelog = "https://github.com/ansible-community/pipc/releases" -documentation = "https://pipc.readthedocs.io/en/latest/" -homepage = "https://github.com/ansible-community/pipc" -repository = "https://github.com/ansible-community/pipc" +changelog = "https://github.com/ansible-community/pipac/releases" +documentation = "https://pipac.readthedocs.io/en/latest/" +homepage = "https://github.com/ansible-community/pipac" +repository = "https://github.com/ansible-community/pipac" [tool.mypy] files = ["src", "tests"] @@ -261,7 +261,7 @@ optional-dependencies.test = {file = [".config/requirements-test.txt"]} [tool.setuptools_scm] local_scheme = "no-local-version" -write_to = "src/pipc/_version.py" +write_to = "src/pip4a/_version.py" [tool.tomlsort] all = true diff --git a/requirements.txt b/requirements.txt index 4ef621f..883b867 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,82 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --extra=docs --extra=test --no-annotate --output-file=requirements.txt +# pip-compile --extra=docs --extra=test --no-annotate --output-file=requirements.txt --strip-extras # -ansible-builder==3.0.0 -ansible-compat==4.1.5 -ansible-core==2.15.2 -ansible-lint==6.17.2 -ansible-navigator==3.4.2 -ansible-runner==2.3.3 -ansible-sign==0.1.1 -attrs==23.1.0 -bindep==2.11.0 -black==23.7.0 -bracex==2.3.post1 cachetools==5.3.1 -certifi==2023.7.22 -cffi==1.15.1 chardet==5.2.0 -charset-normalizer==3.2.0 -click==8.1.6 -click-help-colors==0.9.1 colorama==0.4.6 coverage==7.3.0 -cryptography==41.0.3 distlib==0.3.7 -distro==1.8.0 -docutils==0.20.1 -enrich==1.2.7 execnet==2.0.2 filelock==3.12.2 -idna==3.4 iniconfig==2.0.0 -jinja2==3.0.3 -jsonschema==4.19.0 -jsonschema-specifications==2023.7.1 -lockfile==0.12.2 -markdown-it-py==3.0.0 -markupsafe==2.1.3 -mdurl==0.1.2 -molecule==6.0.0 -mypy-extensions==1.0.0 -onigurumacffi==1.2.0 packaging==23.1 -parsley==1.3 -pathspec==0.11.2 -pbr==5.11.1 -pexpect==4.8.0 platformdirs==3.10.0 pluggy==1.2.0 -ptyprocess==0.7.0 -pycparser==2.21 -pygments==2.16.1 pyproject-api==1.5.3 pytest==7.4.0 -pytest-ansible==4.0.0 pytest-xdist==3.3.1 -python-daemon==3.0.1 -python-gnupg==0.5.1 pyyaml==6.0.1 -referencing==0.30.2 -requests==2.31.0 -requirements-parser==0.5.0 -resolvelib==1.0.1 -rich==13.5.2 -rpds-py==0.9.2 -ruamel-yaml==0.17.32 -ruamel-yaml-clib==0.2.7 -six==1.16.0 -subprocess-tee==0.4.1 -tox==4.8.0 -tox-ansible==2.0.9 -types-setuptools==68.0.0.3 -tzdata==2023.3 -urllib3==2.0.4 +tox==4.9.0 virtualenv==20.24.3 -wcmatch==8.4.1 -yamllint==1.32.0 - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/src/pipc/__init__.py b/src/pip4a/__init__.py similarity index 100% rename from src/pipc/__init__.py rename to src/pip4a/__init__.py diff --git a/src/pipc/__main__.py b/src/pip4a/__main__.py similarity index 61% rename from src/pipc/__main__.py rename to src/pip4a/__main__.py index bc9ef10..4c0d58e 100644 --- a/src/pipc/__main__.py +++ b/src/pip4a/__main__.py @@ -1,7 +1,7 @@ -"""A runpy entry point for pipc. +"""A runpy entry point for pip4a. This makes it possible to invoke CLI -via :command:`python -m pipc`. +via :command:`python -m pip4a`. """ from .cli import main diff --git a/src/pip4a/_version.py b/src/pip4a/_version.py new file mode 100644 index 0000000..1d47388 --- /dev/null +++ b/src/pip4a/_version.py @@ -0,0 +1,4 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +__version__ = version = "0.1.dev18" +__version_tuple__ = version_tuple = (0, 1, "dev18") diff --git a/src/pipc/app.py b/src/pip4a/app.py similarity index 100% rename from src/pipc/app.py rename to src/pip4a/app.py diff --git a/src/pipc/arg_parser.py b/src/pip4a/arg_parser.py similarity index 91% rename from src/pipc/arg_parser.py rename to src/pip4a/arg_parser.py index 18e7954..5df4e9e 100644 --- a/src/pipc/arg_parser.py +++ b/src/pip4a/arg_parser.py @@ -26,10 +26,10 @@ def parse() -> argparse.Namespace: ) install_usage = """Usage: - pipc install . - pipc install -e . - pipc install -e .[test] - python -m pipc install ansible.utils""" + pip4a install . + pip4a install -e . + pip4a install -e .[test] + python -m pip4a install ansible.utils""" install = subparsers.add_parser( "install", diff --git a/src/pipc/cli.py b/src/pip4a/cli.py similarity index 97% rename from src/pipc/cli.py rename to src/pip4a/cli.py index 268678a..6319d08 100644 --- a/src/pipc/cli.py +++ b/src/pip4a/cli.py @@ -34,7 +34,7 @@ def parse_args(self: Cli) -> None: def init_logger(self: Cli) -> None: """Initialize the logger.""" - logger = logging.getLogger("pipc") + logger = logging.getLogger("pip4a") ch = ExitOnExceptionHandler() ch.setLevel(logging.DEBUG) cf = ColoredFormatter( @@ -49,7 +49,7 @@ def init_logger(self: Cli) -> None: def ensure_isolated(self: Cli) -> None: """Ensure the environment is isolated.""" - logger = logging.getLogger("pipc") + logger = logging.getLogger("pip4a") env_vars = os.environ errors = [] if "ANSIBLE_COLLECTIONS_PATHS" in env_vars: diff --git a/src/pipc/constants.py b/src/pip4a/constants.py similarity index 55% rename from src/pipc/constants.py rename to src/pip4a/constants.py index 68b5e4f..c689641 100644 --- a/src/pipc/constants.py +++ b/src/pip4a/constants.py @@ -1,11 +1,11 @@ -"""Constants, for now, for pipc.""" +"""Constants, for now, for pip4a.""" from pathlib import Path class Constants: - """Constants, for now, for pipc.""" + """Constants, for now, for pip4a.""" TEST_REQUIREMENTS_PY = Path("./test-requirements.txt").resolve() - REQUIMENTS_PY = Path("./requirements.txt").resolve() + REQUIREMENTS_PY = Path("./requirements.txt").resolve() COLLECTION_BUILD_DIR = Path("./build").resolve() diff --git a/src/pipc/installer.py b/src/pip4a/installer.py similarity index 98% rename from src/pipc/installer.py rename to src/pip4a/installer.py index 1992137..742935d 100644 --- a/src/pipc/installer.py +++ b/src/pip4a/installer.py @@ -29,14 +29,14 @@ def __init__(self: Installer, app: App) -> None: """Initialize the installer. Arguments: - args: The CLI arguments + app: The app instance """ self.app: App = app def run(self: Installer) -> None: """Run the installer.""" if self.app.args.collection_specifier.startswith("."): - self._pip_install(C.REQUIMENTS_PY) + self._pip_install(C.REQUIREMENTS_PY) if "[test]" in self.app.args.collection_specifier: self._pip_install(C.TEST_REQUIREMENTS_PY) site_pkg_path = self._install_collection() diff --git a/src/pipc/logger.py b/src/pip4a/logger.py similarity index 93% rename from src/pipc/logger.py rename to src/pip4a/logger.py index 6ce3ecc..7439b15 100644 --- a/src/pipc/logger.py +++ b/src/pip4a/logger.py @@ -1,4 +1,4 @@ -"""Produce coloroed logs.""" +"""Produce pretty logs.""" from __future__ import annotations @@ -59,6 +59,9 @@ def emit(self: ExitOnExceptionHandler, record: logging.LogRecord) -> None: Args: record: The log record + + Raises: + SystemExit: If the log record is an error or critical """ super().emit(record) if record.levelno in (logging.ERROR, logging.CRITICAL): diff --git a/src/pipc/uninstaller.py b/src/pip4a/uninstaller.py similarity index 98% rename from src/pipc/uninstaller.py rename to src/pip4a/uninstaller.py index b828639..90193ed 100644 --- a/src/pipc/uninstaller.py +++ b/src/pip4a/uninstaller.py @@ -28,7 +28,7 @@ def __init__(self: UnInstaller, app: App) -> None: """Initialize the uninstaller. Arguments: - args: The CLI arguments + app: The app instance """ self.app: App = app @@ -42,7 +42,7 @@ def run(self: UnInstaller) -> None: logger.critical(err) return - self._pip_uninstall(C.REQUIMENTS_PY) + self._pip_uninstall(C.REQUIREMENTS_PY) self._pip_uninstall(C.TEST_REQUIREMENTS_PY) self._remove_collections() diff --git a/src/pipc/utils.py b/src/pip4a/utils.py similarity index 86% rename from src/pipc/utils.py rename to src/pip4a/utils.py index c274331..b5e15a6 100644 --- a/src/pipc/utils.py +++ b/src/pip4a/utils.py @@ -13,10 +13,13 @@ def get_galaxy() -> tuple[str, dict[str, str]]: - """Retreive the collection name from the galaxy.yml file. + """Retrieve the collection name from the galaxy.yml file. Returns: str: The collection name and dependencies + + Raises: + SystemExit: If the collection name is not found """ file_name = Path("galaxy.yml").resolve() if not file_name.exists(): @@ -39,4 +42,4 @@ def get_galaxy() -> tuple[str, dict[str, str]]: except KeyError as exc: err = f"Failed to find collection name in {file_name}: {exc}" logger.critical(err) - raise SystemExit(1) # We shouln't be here + raise SystemExit(1) # We shouldn't be here