From 6559162bf6e6fe1352661d470b9efa8ab9f9ccb2 Mon Sep 17 00:00:00 2001 From: Tyler Levy Conde Date: Mon, 26 Aug 2024 17:29:51 -0600 Subject: [PATCH] Setup with hatch --- .github/workflows/test.yml | 6 +- .pre-commit-config.yaml | 35 ---- .pyproject.toml | 24 --- .rstcheck.cfg | 3 - LICENSE | 201 ------------------- noxfile.py | 354 ---------------------------------- pyproject.toml | 104 ++++++++++ pytest.ini | 3 - requirements/base.txt | 3 - requirements/docs.txt | 6 - requirements/py3.10/tests.txt | 120 ------------ requirements/py3.11/tests.txt | 116 ----------- requirements/py3.8/tests.txt | 83 -------- requirements/py3.9/tests.txt | 83 -------- requirements/tests.in | 6 - setup.cfg | 3 - setup.py | 94 --------- run.py => soluble/__main__.py | 0 soluble/version.py | 1 - tests/integration/conftest.py | 4 +- tests/unit/conftest.py | 20 +- 21 files changed, 109 insertions(+), 1160 deletions(-) delete mode 100644 .pyproject.toml delete mode 100644 .rstcheck.cfg delete mode 100644 LICENSE delete mode 100644 noxfile.py create mode 100644 pyproject.toml delete mode 100644 pytest.ini delete mode 100644 requirements/base.txt delete mode 100644 requirements/docs.txt delete mode 100644 requirements/py3.10/tests.txt delete mode 100644 requirements/py3.11/tests.txt delete mode 100644 requirements/py3.8/tests.txt delete mode 100644 requirements/py3.9/tests.txt delete mode 100644 requirements/tests.in delete mode 100644 setup.cfg delete mode 100644 setup.py rename run.py => soluble/__main__.py (100%) delete mode 100644 soluble/version.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce8e348..b7bbf81 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install nox - - name: Test with nox + python -m pip install .[test] + - name: Test with pytest run: | - nox -p ${{ matrix.python-version }} + pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 80a5463..a026adf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -78,34 +78,6 @@ repos: additional_dependencies: [sphinx] # <---- Formatting ----------------------------------------------------------------------------- - # ----- Testing Static Requirements ------------------------------------------------------------------------------------> - - repo: https://github.com/saltstack/pip-tools-compile-impersonate - rev: '4.5' - hooks: - - id: pip-tools-compile - alias: compile-3.10-test-requirements - name: Py3.10 Test Requirements - files: ^requirements/tests.in$ - pass_filenames: false - args: - - -v - - --py-version=3.10 - - --platform=linux - - requirements/tests.in - - - id: pip-tools-compile - alias: compile-3.11-test-requirements - name: Py3.11 Test Requirements - files: ^requirements/tests.in$ - pass_filenames: false - args: - - -v - - --py-version=3.11 - - --platform=linux - - requirements/tests.in - - # <---- Testing Static Requirements ------------------------------------------------------------------------------------- - # ----- Security --------------------------------------------------------------------------------------------------> - repo: https://github.com/PyCQA/bandit rev: "1.7.4" @@ -120,10 +92,3 @@ repos: )$ # <---- Security --------------------------------------------------------------------------------------------------- - - # ----- Build Test --------------------------------------------------------------------------------------------------> - - repo: https://github.com/miki725/pre-commit-twine-check - rev: '0.1' - hooks: - - id: twine-check - # <---- Build Test --------------------------------------------------------------------------------------------------- diff --git a/.pyproject.toml b/.pyproject.toml deleted file mode 100644 index 4ed344e..0000000 --- a/.pyproject.toml +++ /dev/null @@ -1,24 +0,0 @@ -[build-system] -requires = ["setuptools>=64.0.3"] -build-backend = "setuptools.build_meta:__legacy__" - -[tool.black] -line-length = 88 -target-version = ['py38', 'py39', 'py310', 'py311'] -include = '\.pyi?$' -exclude = ''' -( - /( - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - )/ -) -''' diff --git a/.rstcheck.cfg b/.rstcheck.cfg deleted file mode 100644 index 52138a6..0000000 --- a/.rstcheck.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[rstcheck] -ignore_directives= - todo diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a788706..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2024 Tyler Levy Conde - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/noxfile.py b/noxfile.py deleted file mode 100644 index 560d8f9..0000000 --- a/noxfile.py +++ /dev/null @@ -1,354 +0,0 @@ -""" -noxfile -~~~~~~~ -Nox configuration script -""" -# pylint: disable=resource-leakage,3rd-party-module-not-gated -import datetime -import os -import pathlib -import shutil -import sys -from pathlib import Path - -# fmt: off -if __name__ == "__main__": - sys.stderr.write( - "Do not execute this file directly. Use nox instead, it will know how to handle this file\n" - ) - sys.stderr.flush() - exit(1) -# fmt: on - -import nox # isort:skip -from nox.command import CommandFailed # isort:skip - -# Nox options -# Reuse existing virtualenvs -nox.options.reuse_existing_virtualenvs = True -# Don't fail on missing interpreters -nox.options.error_on_missing_interpreters = False - -# Python versions to test against -PYTHON_VERSIONS = ("3", "3.8", "3.9", "3.10", "3.11") -# Be verbose when runing under a CI context -CI_RUN = os.environ.get("CI") -PIP_INSTALL_SILENT = CI_RUN is False -SKIP_REQUIREMENTS_INSTALL = "SKIP_REQUIREMENTS_INSTALL" in os.environ -EXTRA_REQUIREMENTS_INSTALL = os.environ.get("EXTRA_REQUIREMENTS_INSTALL") - -COVERAGE_VERSION_REQUIREMENT = "coverage==5.5" - -# Prevent Python from writing bytecode -os.environ["PYTHONDONTWRITEBYTECODE"] = "1" - -# Global Path Definitions -REPO_ROOT = pathlib.Path(__file__).resolve().parent -# Change current directory to REPO_ROOT -os.chdir(REPO_ROOT) - -ARTIFACTS_DIR = REPO_ROOT / "artifacts" -# Make sure the artifacts directory exists -ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True) - -RUNTESTS_LOGFILE = ARTIFACTS_DIR / "runtests-{}.log".format( - datetime.datetime.now().strftime("%Y%m%d%H%M%S.%f") -) -COVERAGE_REPORT_DB = REPO_ROOT / ".coverage" -COVERAGE_REPORT_PROJECT = ARTIFACTS_DIR.relative_to(REPO_ROOT) / "coverage-project.xml" -COVERAGE_REPORT_TESTS = ARTIFACTS_DIR.relative_to(REPO_ROOT) / "coverage-tests.xml" -JUNIT_REPORT = ARTIFACTS_DIR.relative_to(REPO_ROOT) / "junit-report.xml" - - -def _get_session_python_version_info(session): - try: - version_info = session._runner._real_python_version_info - except AttributeError: - session_py_version = session.run_always( - "python", - "-c" 'import sys; sys.stdout.write("{}.{}.{}".format(*sys.version_info))', - silent=True, - log=False, - ) - version_info = tuple( - int(part) for part in session_py_version.split(".") if part.isdigit() - ) - session._runner._real_python_version_info = version_info - return version_info - - -def _get_pydir(session): - version_info = _get_session_python_version_info(session) - if version_info < (3, 8): - session.error("Only Python >= 3.8 is supported") - return "py{}.{}".format(*version_info) - - -@nox.session(python=PYTHON_VERSIONS) -def tests(session): - if SKIP_REQUIREMENTS_INSTALL is False: - # Always have the wheel package installed - session.install("wheel", silent=PIP_INSTALL_SILENT) - session.install(COVERAGE_VERSION_REQUIREMENT, silent=PIP_INSTALL_SILENT) - - # Install requirements - requirements_file = ( - REPO_ROOT / "requirements" / _get_pydir(session) / "tests.txt" - ) - install_command = [ - "--progress-bar=off", - "-r", - str(requirements_file.relative_to(REPO_ROOT)), - ] - session.install(*install_command, silent=PIP_INSTALL_SILENT) - - if EXTRA_REQUIREMENTS_INSTALL: - session.log( - "Installing the following extra requirements because the " - "EXTRA_REQUIREMENTS_INSTALL environment variable was set: " - "EXTRA_REQUIREMENTS_INSTALL='%s'", - EXTRA_REQUIREMENTS_INSTALL, - ) - install_command = ["--progress-bar=off"] - install_command += [ - req.strip() for req in EXTRA_REQUIREMENTS_INSTALL.split() - ] - session.install(*install_command, silent=PIP_INSTALL_SILENT) - - session.run("coverage", "erase") - args = [ - "--rootdir", - str(REPO_ROOT), - f"--log-file={RUNTESTS_LOGFILE}", - "--log-file-level=debug", - "--show-capture=no", - f"--junitxml={JUNIT_REPORT}", - "--showlocals", - "-ra", - "-s", - ] - if session._runner.global_config.forcecolor: - args.append("--color=yes") - if not session.posargs: - args.append("tests/") - else: - for arg in session.posargs: - if arg.startswith("--color") and args[0].startswith("--color"): - args.pop(0) - args.append(arg) - for arg in session.posargs: - if arg.startswith("-"): - continue - if arg.startswith(f"tests{os.sep}"): - break - try: - pathlib.Path(arg).resolve().relative_to(REPO_ROOT / "tests") - break - except ValueError: - continue - else: - args.append("tests/") - try: - session.run("coverage", "run", "-m", "pytest", *args) - finally: - # Always combine and generate the XML coverage report - try: - session.run("coverage", "combine") - except CommandFailed: - # Sometimes some of the coverage files are corrupt which would - # trigger a CommandFailed exception - pass - # Generate report for salt code coverage - session.run( - "coverage", - "xml", - "-o", - str(COVERAGE_REPORT_PROJECT), - "--omit=tests/*", - "--include=soluble/*", - ) - # Generate report for tests code coverage - session.run( - "coverage", - "xml", - "-o", - str(COVERAGE_REPORT_TESTS), - "--omit=soluble/*", - "--include=tests/*", - ) - # Move the coverage DB to artifacts/coverage in order for it to be archived by CI - if COVERAGE_REPORT_DB.exists(): - shutil.move( - str(COVERAGE_REPORT_DB), str(ARTIFACTS_DIR / COVERAGE_REPORT_DB.name) - ) - - -@nox.session(name="docs-html", python="3") -@nox.parametrize("clean", [False, True]) -def docs_html(session, clean): - """ - Build Sphinx HTML Documentation - """ - _get_pydir(session) - - # Latest pip, setuptools, and wheel - install_command = ["--progress-bar=off", "-U", "pip", "setuptools", "wheel"] - session.install(*install_command, silent=True) - - # Install requirements - requirements_file = Path("requirements", "docs.txt") - install_command = ["--progress-bar=off", "-r", str(requirements_file)] - session.install(*install_command, silent=True) - - build_dir = Path("docs", "_build", "html") - sphinxopts = "-n" # Use W as arg in future - if clean: - sphinxopts += "E" - base_modules_dir = "soluble" - autogen_config = [] - skip_dir_config = ["contracts"] - gen_docs_dir_parent = "docs/ref" - args = ["--separate", "--tocfile", "index", "-f", "-o"] - index_ref_toc = "" - # For each module dir, generate api docs for all subdirs - for dir_target in autogen_config: - module_listing = [] - dir_type = dir_target.split("/")[0] - index_ref_toc += f" ref/{dir_type}/index\n" - sub_dirs = [ - f.path - for f in os.scandir(f"{base_modules_dir}/{dir_target}") - if f.is_dir() - ] - - # Include all functions that might be defined in the base directory - sub_dirs.append(f"{base_modules_dir}/{dir_target}") - sub_dirs.append(f"{base_modules_dir}/{dir_type}") - - # Generate docs/ref dir tree - for sub_dir in sub_dirs: - module_dir = sub_dir.split("/")[-1] - if module_dir in skip_dir_config: - continue - if module_dir == dir_type: - new_module_dir_path = f"{gen_docs_dir_parent}/{module_dir}" - else: - module_listing.append( - " " + module_dir.replace("\\", "/") + "/index\n" - ) - new_module_dir_path = ( - f"{gen_docs_dir_parent}/{dir_type}/{module_dir}" - ) - Path(new_module_dir_path).mkdir(parents=True, exist_ok=True) - session.run( - "sphinx-apidoc", - *args, - new_module_dir_path, - sub_dir, - f"{sub_dir}/init.py", - external=True, - ) - # sphinx-apidoc has a bug where title underlines need to be fixed - # Fix title underlines to appropriate length - new_rst_files = [ - f.path for f in os.scandir(new_module_dir_path) if f.is_file() - ] - for new_rst_file in new_rst_files: - # if this is the top level module, add direct files to index.rst - if module_dir == dir_type and not new_rst_file.endswith( - "index.rst" - ): - module_listing.append( - " " - + f"{(new_rst_file.split('/')[-1]).replace('.rst', '')}\n" - ) - with open(new_rst_file) as target_file_reader: - target_file_content = target_file_reader.read() - new_target_file_content = target_file_content.replace( - ".. automodule:: ", - ".. automodule:: " - + sub_dir.replace("/", ".").replace("\\", ".") - + ".", - ) - rst_file_title_truncated = new_target_file_content.split("\n")[ - 0 - ].replace(" module", "") - rst_file_title_length = len(rst_file_title_truncated) - rst_underline_length = len(new_target_file_content.split("\n")[1]) - if ( - rst_file_title_length != rst_underline_length - or rst_file_title_length < 3 - ): - if rst_file_title_length < 3: - rst_new_underline = "===" - else: - rst_new_underline = "=" * rst_file_title_length - new_target_file_content = new_target_file_content.replace( - new_target_file_content.split("\n")[1], rst_new_underline - ) - new_target_file_content = new_target_file_content.replace( - new_target_file_content.split("\n")[0], rst_file_title_truncated - ) - with open(new_rst_file, "w") as target_file_writer: - target_file_writer.write(new_target_file_content) - # Write root index file for all modules - dir_type_title = f"{dir_type} modules" - underline_length = len(dir_type_title) - underline = "=" * underline_length - module_listing.sort() - index_contents = [ - f"{dir_type_title}\n", - f"{underline}\n", - "\n", - ".. include:: /_includes/modindex-note.rst\n", - "\n", - ".. toctree::\n", - " :maxdepth: 2\n", - "\n", - ] + module_listing - # Populate main base module index with all sub-indices - with open(f"{gen_docs_dir_parent}/{dir_type}/index.rst", "w") as rst_file: - rst_file.writelines(index_contents) - # Create TOC reference section - with open("docs/_includes/reference-toc-template.rst") as target_file_reader: - target_file_content = target_file_reader.read() - new_target_file_content = target_file_content.replace( - " .. REF_PLACEHOLDER\n", index_ref_toc - ) - with open("docs/_includes/reference-toc.rst", "w") as target_file_writer: - target_file_writer.write(new_target_file_content) - args = [sphinxopts, "--keep-going", "docs", str(build_dir)] - - session.run("sphinx-build", *args, external=True) - - -@nox.session(python="3") -def docs(session) -> None: - """ - Build and serve the Sphinx HTML documentation, with live reloading on file changes, via sphinx-autobuild. - - Note: Only use this in INTERACTIVE DEVELOPMENT MODE. This SHOULD NOT be called - in CI/CD pipelines, as it will hang. - """ - _get_pydir(session) - - # Latest pip, setuptools, and wheel - install_command = ["--progress-bar=off", "-U", "pip", "setuptools", "wheel"] - session.install(*install_command, silent=True) - - # Install requirements - requirements_file = Path("requirements", "docs.txt") - install_command = ["--progress-bar=off", "-r", str(requirements_file)] - session.install(*install_command, silent=True) - - # Install autobuild req - install_command = ["--progress-bar=off", "-U", "sphinx-autobuild"] - session.install(*install_command, silent=True) - - # Launching LIVE reloading Sphinx session - build_dir = Path("docs", "_build", "html") - args = ["--watch", ".", "--open-browser", "docs", str(build_dir)] - if build_dir.exists(): - shutil.rmtree(build_dir) - - session.run("sphinx-autobuild", *args) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..905b4c8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,104 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "soluble" +version = "1.0.0" +description = "Set up a dissolvable, ephemeral salt minion using salt-ssh" +readme = "README.rst" +url = "https://github.com/saltstack/soluble" +authors = [ + {name = "Tyler Levy Conde", email = "tyler.levy-conde@broadcomm.com"}, +] +classifiers = [ + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: Apache Software License", +] +requires-python = ">=3.10" +dependencies = [ + "pop>=27.1.0", + "distro", + "salt-ssh", + "importlib-metadata<5.0", +] +packages = ["soluble/**"] + +[tool.hatch.build.targets.sdist] +include = ["soluble/**"] + +[tool.hatch.build.targets.wheel] +packages = ["soluble"] + +[project.optional-dependencies] +test = [ + "asynctest", + "mock", + "pytest>=6.2.5", + "pytest-asyncio", + "pytest-pop>=8.0", +] + +# Specify console scripts +[project.scripts] +soluble = "soluble.scripts:start" + +[tool.pytest.ini_options] +testpaths = "tests" +addopts = "--tb native --full-trace --color=yes -vv" +asyncio_mode = "auto" + +[tool.hatch.envs.lint] +detached = true +dependencies = [ + "black>=23.1.0", + "mypy>=1.0.0", + "ruff>=0.0.243", + "hatchling", +] + +[tool.hatch.envs.lint.scripts] +typing = "mypy --install-types --non-interactive {args:hatch_build_scripts tests}" +style = [ + "black --check --diff {args:.}", +] + +fix = [ + "black {args:.}", + "style", +] + +all = [ + "style", + "typing", +] + +[tool.black] +line-length = 88 +target-version = ['py310'] +include = '\.pyi?$' +exclude = ''' +( +/( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +) +''' diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index a14b8cb..0000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -testpaths = tests -addopts = --tb native diff --git a/requirements/base.txt b/requirements/base.txt deleted file mode 100644 index 7e9050a..0000000 --- a/requirements/base.txt +++ /dev/null @@ -1,3 +0,0 @@ -pop>=27.1.0 -salt-ssh -importlib-metadata<5.0 diff --git a/requirements/docs.txt b/requirements/docs.txt deleted file mode 100644 index 7884fa0..0000000 --- a/requirements/docs.txt +++ /dev/null @@ -1,6 +0,0 @@ -sphinx==7.1.2 -sphinx-design -furo>=2022.9.29 -sphinx-copybutton>=0.5.0 -Sphinx-Substitution-Extensions>=2022.2.16 -sphinx-notfound-page>=0.8.3 diff --git a/requirements/py3.10/tests.txt b/requirements/py3.10/tests.txt deleted file mode 100644 index 4f6eba3..0000000 --- a/requirements/py3.10/tests.txt +++ /dev/null @@ -1,120 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# pip-compile --output-file=requirements/py3.10/tests.txt requirements/tests.in -# -aiofiles==24.1.0 - # via dict-toolbox -asynctest==0.13.0 - # via -r requirements/tests.in -certifi==2024.7.4 - # via requests -charset-normalizer==3.3.2 - # via requests -colorama==0.4.6 - # via rend -contextvars==2.4 - # via salt-ssh -dict-toolbox==5.0.0 - # via - # pop - # pop-config - # pytest-pop - # rend -distro==1.9.0 - # via salt-ssh -exceptiongroup==1.2.2 - # via pytest -idna==3.7 - # via requests -immutables==0.20 - # via contextvars -importlib-metadata==4.13.0 - # via soluble -iniconfig==2.0.0 - # via pytest -jinja2==3.1.4 - # via - # rend - # salt-ssh -jmespath==1.0.1 - # via salt-ssh -lazy-object-proxy==1.10.0 - # via pop -markupsafe==2.1.5 - # via - # jinja2 - # salt-ssh -mock==5.1.0 - # via - # -r requirements/tests.in - # pytest-pop -msgpack==1.0.8 - # via - # dict-toolbox - # salt-ssh -nest-asyncio==1.6.0 - # via - # pop-loop - # pytest-pop -packaging==24.1 - # via pytest -pluggy==1.5.0 - # via pytest -pop-config==12.0.4 - # via - # pop - # pytest-pop -pop-loop==1.1.0 - # via pop -pop==27.1.0 - # via - # pop-config - # pop-loop - # pytest-pop - # rend - # soluble -psutil==6.0.0 - # via salt-ssh -pycryptodomex==3.20.0 - # via salt-ssh -pytest-async==0.1.1 - # via pytest-pop -pytest-asyncio==0.18.3 - # via - # -r requirements/tests.in - # pytest-pop -pytest-pop==12.0.0 - # via -r requirements/tests.in -pytest==8.3.2 - # via - # -r requirements/tests.in - # pytest-asyncio - # pytest-pop -pyyaml==6.0.2 - # via - # dict-toolbox - # pop - # rend - # salt-ssh -pyzmq==26.1.1 - # via salt-ssh -rend==7.0.2 - # via pop-config -requests==2.32.3 - # via salt-ssh -salt-ssh==9000 - # via soluble -sniffio==1.3.1 - # via pop-loop -file:. - # via -r requirements/tests.in -toml==0.10.2 - # via rend -tomli==2.0.1 - # via pytest -urllib3==2.2.2 - # via requests -zipp==3.20.0 - # via importlib-metadata diff --git a/requirements/py3.11/tests.txt b/requirements/py3.11/tests.txt deleted file mode 100644 index 040d1c8..0000000 --- a/requirements/py3.11/tests.txt +++ /dev/null @@ -1,116 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# pip-compile --output-file=requirements/py3.11/tests.txt requirements/tests.in -# -aiofiles==24.1.0 - # via dict-toolbox -asynctest==0.13.0 - # via -r requirements/tests.in -certifi==2024.7.4 - # via requests -charset-normalizer==3.3.2 - # via requests -colorama==0.4.6 - # via rend -contextvars==2.4 - # via salt-ssh -dict-toolbox==5.0.0 - # via - # pop - # pop-config - # pytest-pop - # rend -distro==1.9.0 - # via salt-ssh -idna==3.7 - # via requests -immutables==0.20 - # via contextvars -importlib-metadata==4.13.0 - # via soluble -iniconfig==2.0.0 - # via pytest -jinja2==3.1.4 - # via - # rend - # salt-ssh -jmespath==1.0.1 - # via salt-ssh -lazy-object-proxy==1.10.0 - # via pop -markupsafe==2.1.5 - # via - # jinja2 - # salt-ssh -mock==5.1.0 - # via - # -r requirements/tests.in - # pytest-pop -msgpack==1.0.8 - # via - # dict-toolbox - # salt-ssh -nest-asyncio==1.6.0 - # via - # pop-loop - # pytest-pop -packaging==24.1 - # via pytest -pluggy==1.5.0 - # via pytest -pop-config==12.0.4 - # via - # pop - # pytest-pop -pop-loop==1.1.0 - # via pop -pop==27.1.0 - # via - # pop-config - # pop-loop - # pytest-pop - # rend - # soluble -psutil==6.0.0 - # via salt-ssh -pycryptodomex==3.20.0 - # via salt-ssh -pytest-async==0.1.1 - # via pytest-pop -pytest-asyncio==0.18.3 - # via - # -r requirements/tests.in - # pytest-pop -pytest-pop==12.0.0 - # via -r requirements/tests.in -pytest==8.3.2 - # via - # -r requirements/tests.in - # pytest-asyncio - # pytest-pop -pyyaml==6.0.2 - # via - # dict-toolbox - # pop - # rend - # salt-ssh -pyzmq==26.1.1 - # via salt-ssh -rend==7.0.2 - # via pop-config -requests==2.32.3 - # via salt-ssh -salt-ssh==9000 - # via soluble -sniffio==1.3.1 - # via pop-loop -file:. - # via -r requirements/tests.in -toml==0.10.2 - # via rend -urllib3==2.2.2 - # via requests -zipp==3.20.0 - # via importlib-metadata diff --git a/requirements/py3.8/tests.txt b/requirements/py3.8/tests.txt deleted file mode 100644 index 9b4310e..0000000 --- a/requirements/py3.8/tests.txt +++ /dev/null @@ -1,83 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# pip-compile --output-file=requirements/py3.8/tests.txt requirements/tests.in -# -aiofiles==24.1.0 - # via dict-toolbox -asynctest==0.13.0 - # via -r requirements/tests.in -colorama==0.4.6 - # via rend -dict-toolbox==5.0.0 - # via - # pop - # pop-config - # pytest-pop - # rend -exceptiongroup==1.2.2 - # via pytest -iniconfig==2.0.0 - # via pytest -jinja2==3.1.4 - # via rend -lazy-object-proxy==1.10.0 - # via pop -markupsafe==2.1.5 - # via jinja2 -mock==5.1.0 - # via - # -r requirements/tests.in - # pytest-pop -msgpack==1.0.8 - # via dict-toolbox -nest-asyncio==1.6.0 - # via - # pop-loop - # pytest-pop -packaging==24.1 - # via pytest -pluggy==1.5.0 - # via pytest -pop-config==12.0.4 - # via - # pop - # pytest-pop -pop-loop==1.1.0 - # via pop -pop==27.1.0 - # via - # pop-config - # pop-loop - # pytest-pop - # rend - # soluble -pytest-async==0.1.1 - # via pytest-pop -pytest-asyncio==0.18.3 - # via - # -r requirements/tests.in - # pytest-pop -pytest-pop==12.0.0 - # via -r requirements/tests.in -pytest==8.3.2 - # via - # -r requirements/tests.in - # pytest-asyncio - # pytest-pop -pyyaml==6.0.2 - # via - # dict-toolbox - # pop - # rend -rend==7.0.2 - # via pop-config -sniffio==1.3.1 - # via pop-loop -file:. - # via -r requirements/tests.in -toml==0.10.2 - # via rend -tomli==2.0.1 - # via pytest diff --git a/requirements/py3.9/tests.txt b/requirements/py3.9/tests.txt deleted file mode 100644 index 1f536db..0000000 --- a/requirements/py3.9/tests.txt +++ /dev/null @@ -1,83 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# pip-compile --output-file=requirements/py3.9/tests.txt requirements/tests.in -# -aiofiles==24.1.0 - # via dict-toolbox -asynctest==0.13.0 - # via -r requirements/tests.in -colorama==0.4.6 - # via rend -dict-toolbox==5.0.0 - # via - # pop - # pop-config - # pytest-pop - # rend -exceptiongroup==1.2.2 - # via pytest -iniconfig==2.0.0 - # via pytest -jinja2==3.1.4 - # via rend -lazy-object-proxy==1.10.0 - # via pop -markupsafe==2.1.5 - # via jinja2 -mock==5.1.0 - # via - # -r requirements/tests.in - # pytest-pop -msgpack==1.0.8 - # via dict-toolbox -nest-asyncio==1.6.0 - # via - # pop-loop - # pytest-pop -packaging==24.1 - # via pytest -pluggy==1.5.0 - # via pytest -pop-config==12.0.4 - # via - # pop - # pytest-pop -pop-loop==1.1.0 - # via pop -pop==27.1.0 - # via - # pop-config - # pop-loop - # pytest-pop - # rend - # soluble -pytest-async==0.1.1 - # via pytest-pop -pytest-asyncio==0.18.3 - # via - # -r requirements/tests.in - # pytest-pop -pytest-pop==12.0.0 - # via -r requirements/tests.in -pytest==8.3.2 - # via - # -r requirements/tests.in - # pytest-asyncio - # pytest-pop -pyyaml==6.0.2 - # via - # dict-toolbox - # pop - # rend -rend==7.0.2 - # via pop-config -sniffio==1.3.1 - # via pop-loop -file:. - # via -r requirements/tests.in -toml==0.10.2 - # via rend -tomli==2.0.1 - # via pytest diff --git a/requirements/tests.in b/requirements/tests.in deleted file mode 100644 index 30bba0b..0000000 --- a/requirements/tests.in +++ /dev/null @@ -1,6 +0,0 @@ -file:. -asynctest -mock -pytest>=6.2.5 -pytest-asyncio -pytest-pop>=8.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c3559b9..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[sdist] -owner = root -group = root diff --git a/setup.py b/setup.py deleted file mode 100644 index 7077e34..0000000 --- a/setup.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 -import os -import pathlib -import shutil - -from setuptools import Command -from setuptools import setup - -NAME = "soluble" -DESC = "Set up a dissolvable, ephemeral salt minion using salt-ssh" - -# Version info -- read without importing -_locals = {} -with pathlib.Path("soluble", "version.py").open() as fp: - exec(fp.read(), None, _locals) -VERSION = _locals["version"] -SETUP_DIRNAME = os.path.dirname(__file__) -if not SETUP_DIRNAME: - SETUP_DIRNAME = os.getcwd() - -with open("README.rst", encoding="utf-8") as f: - LONG_DESC = f.read() - -with open("requirements/base.txt") as f: - REQUIREMENTS = f.read().splitlines() - -REQUIREMENTS_EXTRA = {} -EXTRA_PATH = pathlib.Path("requirements", "extra") -if EXTRA_PATH.exists(): - REQUIREMENTS_EXTRA["full"] = set() - for extra in EXTRA_PATH.iterdir(): - with extra.open("r") as f: - REQUIREMENTS_EXTRA[extra.stem] = f.read().splitlines() - REQUIREMENTS_EXTRA["full"].update(REQUIREMENTS_EXTRA[extra.stem]) - - -class Clean(Command): - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - for subdir in (NAME, "tests"): - for root, dirs, files in os.walk( - os.path.join(os.path.dirname(__file__), subdir) - ): - for dir_ in dirs: - if dir_ == "__pycache__": - shutil.rmtree(os.path.join(root, dir_)) - - -def discover_packages(): - modules = [] - for package in (NAME,): - for root, _, files in os.walk(os.path.join(SETUP_DIRNAME, package)): - pdir = os.path.relpath(root, SETUP_DIRNAME) - modname = pdir.replace(os.sep, ".") - modules.append(modname) - return modules - - -setup( - name="soluble", - author="Tyler Levy Conde", - author_email="tyler.levy-conde@broadcom.com", - url="https://github.com/akm0d/soluble", - version=VERSION, - install_requires=REQUIREMENTS, - extras_require=REQUIREMENTS_EXTRA, - description=DESC, - long_description=LONG_DESC, - long_description_content_type="text/x-rst", - license="Apache Software License 2.0", - python_requires=">=3.10", - classifiers=[ - "Environment :: Console", - "Intended Audience :: Developers", - "Intended Audience :: Information Technology", - "Intended Audience :: System Administrators", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: Apache Software License", - ], - packages=discover_packages(), - entry_points={"console_scripts": ["soluble = soluble.scripts:start"]}, - cmdclass={"clean": Clean}, -) diff --git a/run.py b/soluble/__main__.py similarity index 100% rename from run.py rename to soluble/__main__.py diff --git a/soluble/version.py b/soluble/version.py deleted file mode 100644 index 11a716e..0000000 --- a/soluble/version.py +++ /dev/null @@ -1 +0,0 @@ -version = "1.0.0" diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 68fabe5..5695d2e 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,12 +1,10 @@ from unittest import mock -import pop.hub import pytest @pytest.fixture(scope="session", name="hub") -def integration_hub(): - hub = pop.hub.Hub() +def integration_hub(hub): hub.pop.sub.add(dyne_name="soluble") with mock.patch("sys.argv", ["soluble"]): diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 8373450..1bf5edf 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -5,28 +5,10 @@ @pytest.fixture(scope="session", name="hub") def unit_hub(hub): - for dyne in ["soluble"]: - hub.pop.sub.add(dyne_name=dyne) + hub.pop.sub.add(dyne_name="soluble") yield hub -async def _setup_hub(hub): - # Set up the hub before each function here - ... - - -async def _teardown_hub(hub): - # Clean up the hub after each function here - ... - - -@pytest.fixture(scope="function", autouse=True) -async def function_hub_wrapper(hub): - await _setup_hub(hub) - yield - await _teardown_hub(hub) - - @pytest.fixture(scope="function") def mock_hub(hub): m_hub = hub.pop.testing.mock_hub()