From d3cacf0c9dd313d75111c069fd7d6b39fe407bed Mon Sep 17 00:00:00 2001 From: guillemdb Date: Wed, 4 Sep 2024 19:55:44 +0200 Subject: [PATCH 01/12] Update pyproject.toml Signed-off-by: guillemdb --- pyproject.toml | 273 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 186 insertions(+), 87 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 21c038b0..14dcd899 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,99 +1,198 @@ +[project] +name = "mloq" +dynamic = ["version"] +description = "Package for initializing ML projects following ML Ops best practices." +readme = "README.md" +license = {text = "MIT"} +authors = [ + {name = "FragileTech", email = "guillem@fragile.tech"} +] +requires-python = ">=3.9" +homepage = "https://github.com/FragileTech/ml-ops-quickstart" +repository = "https://github.com/FragileTech/ml-ops-quickstart" +keywords = ["Machine learning", "artificial intelligence"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Software Development :: Libraries" +] +dependencies = [ + "flogging", + "jinja2", + "click", + "invoke", + "hydra-core", + "param", + "pre-commit", +] + +[project.scripts] +mloq = "mloq.cli:cli" + [build-system] -requires = ["setuptools >= 50.3.2", "wheel >= 0.29.0"] -build-backend = "setuptools.build_meta" +requires = ["hatchling"] +build-backend = "hatchling.build" +[tool.hatch.metadata] +allow-direct-references = true +[tool.hatch.version] +path = "src/mloq/version.py" + +[tool.rye] +dev-dependencies = [ + "ruff", + "sphinx", + "linkify-it-py", + "myst-parser", + "myst-nb", + "ruyaml", + "sphinx-autoapi", + "pydata-sphinx-theme", + "sphinx-autodoc2", + "sphinxcontrib-mermaid", + "sphinx_book_theme", + "sphinx_rtd_theme", + "jupyter-cache", + "sphinx-copybutton", + "sphinx-togglebutton", + "sphinxext-opengraph", + "sphinxcontrib-bibtex", + "psutil>=5.8.0", + "pytest>=6.2.5", + "pytest-cov>=3.0.0", + "pytest-xdist>=2.4.0", + "pytest-rerunfailures>=10.2", + "pyvirtualdisplay>=1.3.2", + "tomli>=1.2.3", + "hypothesis>=6.24.6" + +] +universal = true -# black is the tool to format the source code -[tool.black] +[tool.rye.scripts] +style = { chain = ["ruff check --fix-only --unsafe-fixes tests src", "ruff format tests src"] } +check = { chain = ["ruff check --diff tests src", "ruff format --diff tests src"]} #,"mypy src tests" ] } +test = { chain = ["test:doctest", "test:parallel", "test:singlecore"] } +codecov = { chain = ["codecov:singlecore", "codecov:parallel"] } +"test:parallel" = { cmd = "pytest -n auto -s -o log_cli=true -o log_cli_level=info tests" } +"test:singlecore" = { cmd = "pytest -s -o log_cli=true -o log_cli_level=info tests/control/test_classic_control.py" } +"test:doctest" = { cmd = "pytest --doctest-modules -n 0 -s -o log_cli=true -o log_cli_level=info src" } +"codecov:parallel" = { cmd = "pytest -n auto -s -o log_cli=true -o log_cli_level=info --cov=./ --cov-report=xml --cov-config=pyproject.toml tests" } +docs = {chain = ["build-docs", "serve-docs"]} +build-docs = { cmd = "sphinx-build -b html docs/source docs/build"} +serve-docs = { cmd = "python3 -m http.server --directory docs/build" } + +[tool.ruff] +# Assume Python 3.10 +target-version = "py310" +preview = true +include = ["*.py", "*.pyi", "**/pyproject.toml"]#, "*.ipynb"] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".idea", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "output", + "venv", + "experimental", + ".pytest_cache", + "**/.ipynb_checkpoints/**", + "**/proto/**", + "data", + "config", +] +# Same as Black. line-length = 99 -target-version = ['py37', 'py38', 'py39'] -include = '\.pyi?$' -exclude = ''' -/( - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - | venv -)/ -''' -# isort orders and lints imports -[tool.isort] -profile = "black" -line_length = 99 -multi_line_output = 3 -order_by_type = false -force_alphabetical_sort_within_sections = true -force_sort_within_sections = true -combine_as_imports = true -include_trailing_comma = true -color_output = true -lines_after_imports = 2 -honor_noqa = true +[tool.ruff.lint] +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +select = [ + "ARG", "C4", "D", "E", "EM", "F", "FBT", + "FLY", "FIX", "FURB", "N", "NPY", + "INP", "ISC", "PERF", "PIE", "PL", + "PTH", "RET", "RUF", "S", "T10", + "TD", "T20", "UP", "YTT", "W", +] +ignore = [ + "D100", "D211", "D213", "D104", "D203", "D301", "D407", "S101", + "FBT001", "FBT002", "FIX002", "ISC001", "PLR0913", "RUF012", "TD003", + "PTH123", "PLR6301", "PLR0917", "S311", "S403", "PLR0914", "PLR0915", "S608", + "EM102", "PTH111", "FIX004", "UP035", "PLW2901", "S318", "S408", 'S405', + 'E902', "TD001", "TD002", "FIX001", +] +# Allow autofix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = ["I"] + +[tool.ruff.lint.flake8-quotes] +docstring-quotes = "double" + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["E402", "F401"] +"cli.py" = ["PLC0415", "D205", "D400", "D415"] +"core.py" = ["ARG002", "PLR0904"] +"_old_core.py" = ["ALL"] +"lunar_lander.py" = ["PLR2004", "FBT003", "N806"] +"api_tests.py" = ["D", "ARG002", "PLW1508", "FBT003", "PLR2004"] +"montezuma.py" = ["PLR2004", "S101", "ARG002", "TD002"] +"registry.py" = ["PLC0415", "PLR0911"] +"**/docs/**" = ["INP001", "PTH100"] +"**/super_mario_gym/**" = ["ALL"] +"**/{tests,docs,tools}/*" = [ + "E402", "F401", "F811", "D", "S101", "PLR2004", "S105", + "PLW1514", "PTH123", "PTH107", "N811", "PLC0415", "ARG002", +] +# Enable reformatting of code snippets in docstrings. +[tool.ruff.format] +docstring-code-line-length = 80 +docstring-code-format = true +indent-style = "space" +line-ending = "auto" +preview = true +quote-style = "double" + +[tool.mypy] +exclude = ["experimental.*", "deprecated.*"] +ignore_missing_imports = true -# Code coverage config [tool.coverage.run] branch = true source = ["src/mloq"] [tool.coverage.report] -exclude_lines =["no cover", - 'raise NotImplementedError', - 'if __name__ == "__main__":'] +exclude_lines = [ + "no cover", + "raise NotImplementedError", + "if __name__ == '__main__':" +] ignore_errors = true omit = ["tests/*"] -# Flakehell config -[tool.flakehell] -# optionally inherit from remote config (or local if you want) -base = "https://raw.githubusercontent.com/life4/flakehell/master/pyproject.toml" -# specify any flake8 options. For example, exclude "example.py": -exclude = [".git", "docs", ".ipynb*", "*.ipynb", ".pytest_cache"] -format = "grouped" # make output nice -max_line_length = 99 # show line of source code in output -show_source = true -inline_quotes='"' -import_order_style = "appnexus" -application_package_names = ["mloq"] -application_import_names = ["mloq"] -# Fix AttributeError: 'Namespace' object has no attribute 'extended_default_ignore' -extended_default_ignore=[] - -[tool.flakehell.plugins] -"flake8*" = ["+*"] -pylint = ["+*"] -pyflakes = ["+*"] -pycodestyle = ["+*" , "-D100", "-D104", "-D301", "-W503", "-W504"] - -[tool.flakehell.exceptions."**/__init__.py"] -pyflakes = ["-F401"] - -# No docs in the tests. No unused imports (otherwise pytest fixtures raise errors). -[tool.flakehell.exceptions."**/tests/*"] -pycodestyle = ["-D*"] -"flake8*" = ["-D*"] -pylint = ["-D*"] -pyflakes = ["-F401", "-F811"] - -[tool.pylint.master] -ignore = 'tests' -load-plugins =' pylint.extensions.docparams' - -[tool.pylint.messages_control] -disable = 'all,' -enable = """, - missing-param-doc, - differing-param-doc, - differing-type-doc, - missing-return-doc, - """ - -[tool.flakehell.exceptions."**/assets/*"] -pycodestyle = ["-*"] -pyflakes = ["-*"] -"flake8*" = ["-*"] \ No newline at end of file +[tool.pytest.ini_options] +testpaths = ["tests"] +# To disable a specific warning --> action:message:category:module:line +filterwarnings = ["ignore::UserWarning", 'ignore::DeprecationWarning'] +addopts = "--ignore=scripts --doctest-continue-on-failure" From e07332e9baa81a90719a01c85e938fb703fac717 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Wed, 25 Sep 2024 19:36:00 +0200 Subject: [PATCH 02/12] Port packaging to rye Signed-off-by: guillemdb --- requirements.txt => _requirements.txt | 0 pyproject.toml | 8 +- requirements-dev.lock | 331 ++++++++++++++++++++++++++ requirements.lock | 64 +++++ 4 files changed, 399 insertions(+), 4 deletions(-) rename requirements.txt => _requirements.txt (100%) create mode 100644 requirements-dev.lock create mode 100644 requirements.lock diff --git a/requirements.txt b/_requirements.txt similarity index 100% rename from requirements.txt rename to _requirements.txt diff --git a/pyproject.toml b/pyproject.toml index 14dcd899..3f0beec0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "hydra-core", "param", "pre-commit", + "mypy>=1.11.2", ] [project.scripts] @@ -77,12 +78,11 @@ universal = true [tool.rye.scripts] style = { chain = ["ruff check --fix-only --unsafe-fixes tests src", "ruff format tests src"] } check = { chain = ["ruff check --diff tests src", "ruff format --diff tests src"]} #,"mypy src tests" ] } -test = { chain = ["test:doctest", "test:parallel", "test:singlecore"] } -codecov = { chain = ["codecov:singlecore", "codecov:parallel"] } +test = { chain = ["test:doctest", "test:parallel"] } +codecov = { cmd = "pytest -n auto -s -o log_cli=true -o log_cli_level=info --cov=./ --cov-report=xml --cov-config=pyproject.toml tests" } "test:parallel" = { cmd = "pytest -n auto -s -o log_cli=true -o log_cli_level=info tests" } -"test:singlecore" = { cmd = "pytest -s -o log_cli=true -o log_cli_level=info tests/control/test_classic_control.py" } +"test:singlecore" = { cmd = "pytest -s -o log_cli=true -o log_cli_level=info tests" } "test:doctest" = { cmd = "pytest --doctest-modules -n 0 -s -o log_cli=true -o log_cli_level=info src" } -"codecov:parallel" = { cmd = "pytest -n auto -s -o log_cli=true -o log_cli_level=info --cov=./ --cov-report=xml --cov-config=pyproject.toml tests" } docs = {chain = ["build-docs", "serve-docs"]} build-docs = { cmd = "sphinx-build -b html docs/source docs/build"} serve-docs = { cmd = "python3 -m http.server --directory docs/build" } diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 00000000..47eff25f --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,331 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: true +# with-sources: false +# generate-hashes: false +# universal: true + +-e file:. +accessible-pygments==0.0.5 + # via pydata-sphinx-theme +alabaster==1.0.0 + # via sphinx +antlr4-python3-runtime==4.9.3 + # via hydra-core + # via omegaconf +appnope==0.1.4 ; platform_system == 'Darwin' + # via ipykernel +astroid==3.3.4 + # via sphinx-autoapi + # via sphinx-autodoc2 +asttokens==2.4.1 + # via stack-data +attrs==24.2.0 + # via hypothesis + # via jsonschema + # via jupyter-cache + # via referencing +babel==2.16.0 + # via pydata-sphinx-theme + # via sphinx +beautifulsoup4==4.12.3 + # via pydata-sphinx-theme +certifi==2024.8.30 + # via requests +cffi==1.17.1 ; implementation_name == 'pypy' + # via pyzmq +cfgv==3.4.0 + # via pre-commit +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via jupyter-cache + # via mloq +colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows' + # via click + # via ipython + # via pytest + # via sphinx +comm==0.2.2 + # via ipykernel +coverage==7.6.1 + # via pytest-cov +debugpy==1.8.6 + # via ipykernel +decorator==5.1.1 + # via ipython +distlib==0.3.8 + # via virtualenv +distro==1.9.0 + # via ruyaml +docutils==0.21.2 + # via myst-parser + # via pybtex-docutils + # via pydata-sphinx-theme + # via sphinx + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +exceptiongroup==1.2.2 ; python_full_version < '3.11' + # via hypothesis + # via ipython + # via pytest +execnet==2.1.1 + # via pytest-xdist +executing==2.1.0 + # via stack-data +fastjsonschema==2.20.0 + # via nbformat +filelock==3.16.1 + # via virtualenv +flogging==0.0.23 + # via mloq +greenlet==3.1.1 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64') + # via sqlalchemy +hydra-core==1.3.2 + # via mloq +hypothesis==6.112.1 +identify==2.6.1 + # via pre-commit +idna==3.10 + # via requests +imagesize==1.4.1 + # via sphinx +importlib-metadata==8.5.0 + # via jupyter-cache + # via myst-nb +iniconfig==2.0.0 + # via pytest +invoke==2.2.0 + # via mloq +ipykernel==6.29.5 + # via myst-nb +ipython==8.27.0 + # via ipykernel + # via myst-nb +jedi==0.19.1 + # via ipython +jinja2==3.1.4 + # via mloq + # via myst-parser + # via sphinx + # via sphinx-autoapi +jsonschema==4.23.0 + # via nbformat +jsonschema-specifications==2023.12.1 + # via jsonschema +jupyter-cache==1.0.0 + # via myst-nb +jupyter-client==8.6.3 + # via ipykernel + # via nbclient +jupyter-core==5.7.2 + # via ipykernel + # via jupyter-client + # via nbclient + # via nbformat +latexcodec==3.0.0 + # via pybtex +linkify-it-py==2.0.3 +markdown-it-py==3.0.0 + # via mdit-py-plugins + # via myst-parser +markupsafe==2.1.5 + # via jinja2 +matplotlib-inline==0.1.7 + # via ipykernel + # via ipython +mdit-py-plugins==0.4.2 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +mypy==1.11.2 + # via mloq +mypy-extensions==1.0.0 + # via mypy +myst-nb==1.1.2 +myst-parser==4.0.0 + # via myst-nb +nbclient==0.10.0 + # via jupyter-cache + # via myst-nb +nbformat==5.10.4 + # via jupyter-cache + # via myst-nb + # via nbclient +nest-asyncio==1.6.0 + # via ipykernel +nodeenv==1.9.1 + # via pre-commit +omegaconf==2.3.0 + # via hydra-core +packaging==24.1 + # via hydra-core + # via ipykernel + # via pydata-sphinx-theme + # via pytest + # via pytest-rerunfailures + # via sphinx +param==2.1.1 + # via mloq +parso==0.8.4 + # via jedi +pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' + # via ipython +platformdirs==4.3.6 + # via jupyter-core + # via virtualenv +pluggy==1.5.0 + # via pytest +pre-commit==3.8.0 + # via mloq +prompt-toolkit==3.0.48 + # via ipython +psutil==6.0.0 + # via ipykernel +ptyprocess==0.7.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' + # via pexpect +pure-eval==0.2.3 + # via stack-data +pybtex==0.24.0 + # via pybtex-docutils + # via sphinxcontrib-bibtex +pybtex-docutils==1.0.3 + # via sphinxcontrib-bibtex +pycparser==2.22 ; implementation_name == 'pypy' + # via cffi +pydata-sphinx-theme==0.15.4 + # via sphinx-book-theme +pygments==2.18.0 + # via accessible-pygments + # via ipython + # via pydata-sphinx-theme + # via sphinx +pytest==8.3.3 + # via pytest-cov + # via pytest-rerunfailures + # via pytest-xdist +pytest-cov==5.0.0 +pytest-rerunfailures==14.0 +pytest-xdist==3.6.1 +python-dateutil==2.9.0.post0 + # via jupyter-client +pyvirtualdisplay==3.0 +pywin32==306 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + # via jupyter-core +pyyaml==6.0.2 + # via jupyter-cache + # via myst-nb + # via myst-parser + # via omegaconf + # via pre-commit + # via pybtex + # via sphinx-autoapi +pyzmq==26.2.0 + # via ipykernel + # via jupyter-client +referencing==0.35.1 + # via jsonschema + # via jsonschema-specifications +requests==2.32.3 + # via sphinx +rpds-py==0.20.0 + # via jsonschema + # via referencing +ruff==0.6.7 +ruyaml==0.91.0 +setuptools==75.1.0 + # via ruyaml + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +six==1.16.0 + # via asttokens + # via pybtex + # via python-dateutil +snowballstemmer==2.2.0 + # via sphinx +sortedcontainers==2.4.0 + # via hypothesis +soupsieve==2.6 + # via beautifulsoup4 +sphinx==8.0.2 + # via myst-nb + # via myst-parser + # via pydata-sphinx-theme + # via sphinx-autoapi + # via sphinx-book-theme + # via sphinx-copybutton + # via sphinx-rtd-theme + # via sphinx-togglebutton + # via sphinxcontrib-bibtex + # via sphinxext-opengraph +sphinx-autoapi==3.3.1 +sphinx-autodoc2==0.5.0 +sphinx-book-theme==1.1.3 +sphinx-copybutton==0.5.2 +sphinx-rtd-theme==0.5.1 +sphinx-togglebutton==0.3.2 +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-bibtex==2.6.3 +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-mermaid==0.9.2 +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via sphinx +sphinxext-opengraph==0.9.1 +sqlalchemy==2.0.35 + # via jupyter-cache +stack-data==0.6.3 + # via ipython +tabulate==0.9.0 + # via jupyter-cache +tomli==2.0.1 + # via coverage + # via mypy + # via pytest + # via sphinx + # via sphinx-autodoc2 +tornado==6.4.1 + # via ipykernel + # via jupyter-client +traitlets==5.14.3 + # via comm + # via ipykernel + # via ipython + # via jupyter-client + # via jupyter-core + # via matplotlib-inline + # via nbclient + # via nbformat +typing-extensions==4.12.2 + # via astroid + # via ipython + # via mypy + # via myst-nb + # via pydata-sphinx-theme + # via sphinx-autodoc2 + # via sqlalchemy +uc-micro-py==1.0.3 + # via linkify-it-py +urllib3==2.2.3 + # via requests +virtualenv==20.26.5 + # via pre-commit +wcwidth==0.2.13 + # via prompt-toolkit +wheel==0.44.0 + # via sphinx-togglebutton +xxhash==3.5.0 + # via flogging +zipp==3.20.2 + # via importlib-metadata diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 00000000..b8e68729 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,64 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: true +# with-sources: false +# generate-hashes: false +# universal: true + +-e file:. +antlr4-python3-runtime==4.9.3 + # via hydra-core + # via omegaconf +cfgv==3.4.0 + # via pre-commit +click==8.1.7 + # via mloq +colorama==0.4.6 ; platform_system == 'Windows' + # via click +distlib==0.3.8 + # via virtualenv +filelock==3.16.1 + # via virtualenv +flogging==0.0.23 + # via mloq +hydra-core==1.3.2 + # via mloq +identify==2.6.1 + # via pre-commit +invoke==2.2.0 + # via mloq +jinja2==3.1.4 + # via mloq +markupsafe==2.1.5 + # via jinja2 +mypy==1.11.2 + # via mloq +mypy-extensions==1.0.0 + # via mypy +nodeenv==1.9.1 + # via pre-commit +omegaconf==2.3.0 + # via hydra-core +packaging==24.1 + # via hydra-core +param==2.1.1 + # via mloq +platformdirs==4.3.6 + # via virtualenv +pre-commit==3.8.0 + # via mloq +pyyaml==6.0.2 + # via omegaconf + # via pre-commit +tomli==2.0.1 ; python_full_version < '3.11' + # via mypy +typing-extensions==4.12.2 + # via mypy +virtualenv==20.26.5 + # via pre-commit +xxhash==3.5.0 + # via flogging From 79070282e432fec81a3a64a5ac93854dad06da31 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Wed, 2 Oct 2024 12:54:31 +0200 Subject: [PATCH 03/12] Setup project scafolding Signed-off-by: guillemdb --- ._pre-commit-config.yaml | 19 + .bumpversion.cfg | 29 + .codecov.yml | 19 - .cookiecutterrc | 66 ++ .coveragerc | 16 + .editorconfig | 20 + .github/dependabot.yml | 109 +-- .github/labels.yml | 66 ++ .github/release-drafter.yml | 42 ++ .github/workflows/build.yml | 280 ++++++++ .github/workflows/deploy-docs.yml | 50 -- .github/workflows/documentation.yml | 56 ++ .github/workflows/draft.yml | 19 + .github/workflows/labeler.yml | 24 + .github/workflows/push.yml | 299 -------- .github/workflows/tests.yml | 41 ++ .gitignore | 159 ++--- .pre-commit-config.yaml | 10 - .readthedocs.yml | 14 + AUTHORS.rst | 5 + CHANGELOG.rst | 8 + CODE_OF_CONDUCT.md | 132 ---- CONTRIBUTING.md | 56 -- CONTRIBUTING.rst | 85 +++ DCO.md | 36 - Dockerfile | 30 - LICENSE | 20 +- MANIFEST.in | 22 + Makefile | 52 -- Makefile.docker | 79 -- README.md | 128 ++-- WHAT_MLOQ_GENERATED.md | 30 - __pytest.ini | 30 + _requirements.txt | 9 - docs/Makefile | 40 -- docs/_config.yml | 114 +++ docs/_toc.yml | 9 + docs/favicon.png | Bin 0 -> 51011 bytes docs/images/ci_python.png | Bin 106084 -> 0 bytes docs/images/mloq_setup.png | Bin 447960 -> 0 bytes docs/index.md | 13 + docs/logo.png | Bin 0 -> 49046 bytes docs/logo.svg | 65 ++ docs/make.bat | 35 - docs/markdown-notebooks.md | 53 ++ docs/markdown.md | 55 ++ docs/notebooks.ipynb | 122 ++++ docs/references.bib | 57 ++ docs/requirements-docs.txt | 10 - docs/source/_autoapi_templates/index.rst | 24 - docs/source/_static/mloq.yml | 13 - docs/source/conf.py | 152 ---- docs/source/index.md | 53 -- docs/source/markdown/features.md | 82 --- docs/source/markdown/library.md | 19 - docs/source/markdown/tutorial.md | 11 - docs/source/markdown/usage.md | 64 -- docs/source/markdown/welcome.md | 29 - mloq.yaml | 147 ---- notebooks/__init__.py | 0 pyproject.toml | 336 +++++++-- requirements-dev.lock | 331 --------- requirements-lint.txt | 14 - requirements-test.txt | 6 - requirements.lock | 64 -- setup.py | 63 -- src/mloq.egg-info/PKG-INFO | 86 --- src/mloq.egg-info/SOURCES.txt | 77 -- src/mloq.egg-info/dependency_links.txt | 1 - src/mloq.egg-info/entry_points.txt | 2 - src/mloq.egg-info/requires.txt | 8 - src/mloq.egg-info/top_level.txt | 1 - src/mloq/__init__.py | 14 +- src/mloq/__main__.py | 15 +- src/mloq/_utils.py | 133 ---- src/mloq/assets/ci/push.yml | 259 ------- src/mloq/assets/docker/Dockerfile | 31 - src/mloq/assets/docker/Makefile.docker | 79 -- src/mloq/assets/docs/conf.txt | 140 ---- src/mloq/assets/docs/deploy-docs.yml | 50 -- src/mloq/assets/docs/index.md | 11 - src/mloq/assets/docs/make_bat.txt | 36 - src/mloq/assets/docs/makefile_docs.txt | 40 -- src/mloq/assets/docs/requirements-docs.txt | 10 - src/mloq/assets/license/APACHE_LICENSE | 202 ------ src/mloq/assets/license/DCO.md | 36 - src/mloq/assets/license/GPL_LICENSE | 674 ------------------ src/mloq/assets/license/MIT_LICENSE | 21 - src/mloq/assets/lint/requirements-lint.txt | 14 - src/mloq/assets/mloq/WHAT_MLOQ_GENERATED.md | 4 - src/mloq/assets/mloq/mloq.yaml | 117 --- src/mloq/assets/package/setup.txt | 44 -- src/mloq/assets/project/.codecov.yml | 19 - src/mloq/assets/project/.gitignore | 131 ---- .../assets/project/.pre-commit-config.yaml | 10 - src/mloq/assets/project/CODE_OF_CONDUCT.md | 132 ---- src/mloq/assets/project/CONTRIBUTING.md | 56 -- src/mloq/assets/project/init.txt | 0 src/mloq/assets/project/main.txt | 11 - src/mloq/assets/project/requirements-test.txt | 6 - src/mloq/assets/project/test_main.txt | 5 - src/mloq/assets/project/version.txt | 2 - src/mloq/assets/requirements/data-science.txt | 8 - .../requirements/data-visualization.txt | 9 - src/mloq/assets/requirements/dogfood.txt | 9 - src/mloq/assets/requirements/pytorch.txt | 4 - src/mloq/assets/requirements/requirements.txt | 0 src/mloq/assets/requirements/tensorflow.txt | 1 - src/mloq/assets/shared/Makefile | 53 -- src/mloq/assets/shared/README.md | 8 - src/mloq/assets/shared/pyproject.toml | 163 ----- src/mloq/cli.py | 116 +-- src/mloq/command.py | 179 ----- src/mloq/commands/__init__.py | 11 - src/mloq/commands/ci.py | 65 -- src/mloq/commands/docker.py | 117 --- src/mloq/commands/docs.py | 100 --- src/mloq/commands/globals.py | 35 - src/mloq/commands/license.py | 63 -- src/mloq/commands/lint.py | 50 -- src/mloq/commands/package.py | 78 -- src/mloq/commands/project.py | 141 ---- src/mloq/commands/requirements.py | 212 ------ src/mloq/commands/setup.py | 98 --- src/mloq/config/__init__.py | 0 src/mloq/config/configuration.py | 544 -------------- src/mloq/config/custom_click.py | 188 ----- src/mloq/config/param_patch.py | 59 -- src/mloq/config/prompt.py | 321 --------- src/mloq/core.py | 9 + src/mloq/custom_click.py | 188 ----- src/mloq/failure.py | 17 - src/mloq/files.py | 95 --- src/mloq/git.py | 56 -- src/mloq/record.py | 148 ---- src/mloq/runner.py | 120 ---- src/mloq/templating.py | 71 -- src/mloq/version.py | 3 +- src/mloq/writer.py | 123 ---- tests/__init__.py | 1 - tests/commands/__init__.py | 0 tests/commands/test_ci.py | 159 ----- tests/commands/test_docker.py | 112 --- tests/commands/test_docs.py | 107 --- tests/commands/test_globals.py | 60 -- tests/commands/test_license.py | 169 ----- tests/commands/test_lint.py | 124 ---- tests/commands/test_package.py | 158 ---- tests/commands/test_project.py | 122 ---- tests/commands/test_setup.py | 86 --- tests/config/__init__.py | 0 tests/config/fixtures.py | 189 ----- tests/config/test_configuration.py | 178 ----- tests/config/test_param_patch.py | 40 -- tests/examples/__init__.py | 0 tests/examples/ci/test_1/mloq.yaml | 94 --- .../test_1/target/.github/workflows/push.yml | 251 ------- .../ci/test_1/target/WHAT_MLOQ_GENERATED.md | 3 - tests/examples/ci/test_mloq/mloq.yaml | 141 ---- .../target/.github/workflows/push.yml | 299 -------- .../test_mloq/target/WHAT_MLOQ_GENERATED.md | 3 - tests/examples/docker/test_1/mloq.yaml | 71 -- .../examples/docker/test_1/target/Dockerfile | 30 - .../docker/test_1/target/Makefile.docker | 79 -- .../test_1/target/WHAT_MLOQ_GENERATED.md | 4 - tests/examples/docker/test_cuda/mloq.yaml | 143 ---- .../docker/test_cuda/target/Dockerfile | 30 - .../docker/test_cuda/target/Makefile.docker | 79 -- .../test_cuda/target/WHAT_MLOQ_GENERATED.md | 4 - tests/examples/docker/test_mloq/mloq.yaml | 142 ---- .../docker/test_mloq/target/Dockerfile | 30 - .../docker/test_mloq/target/Makefile.docker | 79 -- .../test_mloq/target/WHAT_MLOQ_GENERATED.md | 4 - tests/examples/docs/test_1/mloq.yaml | 94 --- .../docs/test_1/target/WHAT_MLOQ_GENERATED.md | 7 - .../examples/docs/test_1/target/docs/Makefile | 29 - .../examples/docs/test_1/target/docs/make.bat | 35 - .../test_1/target/docs/requirements-docs.txt | 6 - .../docs/test_1/target/docs/source/conf.py | 121 ---- .../docs/test_1/target/docs/source/index.md | 11 - tests/examples/license/test_1/mloq.yaml | 98 --- .../license/test_1/target/CODE_OF_CONDUCT.md | 132 ---- .../license/test_1/target/CONTRIBUTING.md | 56 -- tests/examples/license/test_1/target/DCO.md | 36 - tests/examples/license/test_1/target/LICENSE | 21 - .../test_1/target/WHAT_MLOQ_GENERATED.md | 6 - tests/examples/lint/test_1/mloq.yaml | 95 --- tests/examples/mloq.yaml | 142 ---- tests/examples/package/test_1/mloq.yaml | 97 --- tests/examples/project/test_1/mloq.yaml | 93 --- tests/examples/project/test_mloq/mloq.yaml | 142 ---- .../requirements/test_dogfood/mloq.yaml | 119 ---- .../test_dogfood/target/requirements.txt | 6 - .../requirements/test_ds_tf/mloq.yaml | 122 ---- .../test_ds_tf/target/requirements.txt | 18 - .../setup/test_mloq/target/.codecov.yml | 13 - .../target/.github/workflows/push.yml | 299 -------- .../setup/test_mloq/target/.gitignore | 130 ---- .../test_mloq/target/.pre-commit-config.yaml | 10 - .../setup/test_mloq/target/CODE_OF_CONDUCT.md | 132 ---- .../setup/test_mloq/target/CONTRIBUTING.md | 56 -- tests/examples/setup/test_mloq/target/DCO.md | 36 - .../setup/test_mloq/target/Dockerfile | 30 - tests/examples/setup/test_mloq/target/LICENSE | 21 - .../setup/test_mloq/target/Makefile.docker | 79 -- .../examples/setup/test_mloq/target/README.md | 8 - .../test_mloq/target/WHAT_MLOQ_GENERATED.md | 29 - .../setup/test_mloq/target/docs/Makefile | 29 - .../setup/test_mloq/target/docs/make.bat | 35 - .../target/docs/requirements-docs.txt | 6 - .../test_mloq/target/docs/source/conf.py | 121 ---- .../test_mloq/target/docs/source/index.md | 11 - .../setup/test_mloq/target/pyproject.toml | 98 --- .../test_mloq/target/requirements-lint.txt | 13 - .../test_mloq/target/requirements-test.txt | 6 - .../setup/test_mloq/target/requirements.txt | 6 - .../examples/setup/test_mloq/target/setup.py | 43 -- tests/test_cli.py | 7 + tests/test_command.py | 109 --- tests/test_core.py | 5 + tests/test_record.py | 141 ---- tests/test_runner.py | 132 ---- tests/test_writer.py | 20 - 223 files changed, 1805 insertions(+), 14277 deletions(-) create mode 100644 ._pre-commit-config.yaml create mode 100644 .bumpversion.cfg delete mode 100644 .codecov.yml create mode 100644 .cookiecutterrc create mode 100644 .coveragerc create mode 100644 .editorconfig create mode 100644 .github/labels.yml create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/deploy-docs.yml create mode 100644 .github/workflows/documentation.yml create mode 100644 .github/workflows/draft.yml create mode 100644 .github/workflows/labeler.yml delete mode 100644 .github/workflows/push.yml create mode 100644 .github/workflows/tests.yml delete mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yml create mode 100644 AUTHORS.rst create mode 100644 CHANGELOG.rst delete mode 100644 CODE_OF_CONDUCT.md delete mode 100644 CONTRIBUTING.md create mode 100644 CONTRIBUTING.rst delete mode 100644 DCO.md delete mode 100644 Dockerfile create mode 100644 MANIFEST.in delete mode 100644 Makefile delete mode 100644 Makefile.docker delete mode 100644 WHAT_MLOQ_GENERATED.md create mode 100644 __pytest.ini delete mode 100644 _requirements.txt delete mode 100644 docs/Makefile create mode 100644 docs/_config.yml create mode 100644 docs/_toc.yml create mode 100644 docs/favicon.png delete mode 100644 docs/images/ci_python.png delete mode 100644 docs/images/mloq_setup.png create mode 100644 docs/index.md create mode 100644 docs/logo.png create mode 100644 docs/logo.svg delete mode 100644 docs/make.bat create mode 100644 docs/markdown-notebooks.md create mode 100644 docs/markdown.md create mode 100644 docs/notebooks.ipynb create mode 100644 docs/references.bib delete mode 100644 docs/requirements-docs.txt delete mode 100644 docs/source/_autoapi_templates/index.rst delete mode 100644 docs/source/_static/mloq.yml delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/index.md delete mode 100644 docs/source/markdown/features.md delete mode 100644 docs/source/markdown/library.md delete mode 100644 docs/source/markdown/tutorial.md delete mode 100644 docs/source/markdown/usage.md delete mode 100644 docs/source/markdown/welcome.md delete mode 100644 mloq.yaml delete mode 100644 notebooks/__init__.py delete mode 100644 requirements-dev.lock delete mode 100644 requirements-lint.txt delete mode 100644 requirements-test.txt delete mode 100644 requirements.lock delete mode 100644 setup.py delete mode 100644 src/mloq.egg-info/PKG-INFO delete mode 100644 src/mloq.egg-info/SOURCES.txt delete mode 100644 src/mloq.egg-info/dependency_links.txt delete mode 100644 src/mloq.egg-info/entry_points.txt delete mode 100644 src/mloq.egg-info/requires.txt delete mode 100644 src/mloq.egg-info/top_level.txt delete mode 100644 src/mloq/_utils.py delete mode 100644 src/mloq/assets/ci/push.yml delete mode 100644 src/mloq/assets/docker/Dockerfile delete mode 100644 src/mloq/assets/docker/Makefile.docker delete mode 100644 src/mloq/assets/docs/conf.txt delete mode 100644 src/mloq/assets/docs/deploy-docs.yml delete mode 100644 src/mloq/assets/docs/index.md delete mode 100644 src/mloq/assets/docs/make_bat.txt delete mode 100644 src/mloq/assets/docs/makefile_docs.txt delete mode 100644 src/mloq/assets/docs/requirements-docs.txt delete mode 100644 src/mloq/assets/license/APACHE_LICENSE delete mode 100644 src/mloq/assets/license/DCO.md delete mode 100644 src/mloq/assets/license/GPL_LICENSE delete mode 100644 src/mloq/assets/license/MIT_LICENSE delete mode 100644 src/mloq/assets/lint/requirements-lint.txt delete mode 100644 src/mloq/assets/mloq/WHAT_MLOQ_GENERATED.md delete mode 100644 src/mloq/assets/mloq/mloq.yaml delete mode 100644 src/mloq/assets/package/setup.txt delete mode 100644 src/mloq/assets/project/.codecov.yml delete mode 100644 src/mloq/assets/project/.gitignore delete mode 100644 src/mloq/assets/project/.pre-commit-config.yaml delete mode 100644 src/mloq/assets/project/CODE_OF_CONDUCT.md delete mode 100644 src/mloq/assets/project/CONTRIBUTING.md delete mode 100644 src/mloq/assets/project/init.txt delete mode 100644 src/mloq/assets/project/main.txt delete mode 100644 src/mloq/assets/project/requirements-test.txt delete mode 100644 src/mloq/assets/project/test_main.txt delete mode 100644 src/mloq/assets/project/version.txt delete mode 100644 src/mloq/assets/requirements/data-science.txt delete mode 100644 src/mloq/assets/requirements/data-visualization.txt delete mode 100644 src/mloq/assets/requirements/dogfood.txt delete mode 100644 src/mloq/assets/requirements/pytorch.txt delete mode 100644 src/mloq/assets/requirements/requirements.txt delete mode 100644 src/mloq/assets/requirements/tensorflow.txt delete mode 100644 src/mloq/assets/shared/Makefile delete mode 100644 src/mloq/assets/shared/README.md delete mode 100644 src/mloq/assets/shared/pyproject.toml delete mode 100644 src/mloq/command.py delete mode 100644 src/mloq/commands/__init__.py delete mode 100644 src/mloq/commands/ci.py delete mode 100644 src/mloq/commands/docker.py delete mode 100644 src/mloq/commands/docs.py delete mode 100644 src/mloq/commands/globals.py delete mode 100644 src/mloq/commands/license.py delete mode 100644 src/mloq/commands/lint.py delete mode 100644 src/mloq/commands/package.py delete mode 100644 src/mloq/commands/project.py delete mode 100644 src/mloq/commands/requirements.py delete mode 100644 src/mloq/commands/setup.py delete mode 100644 src/mloq/config/__init__.py delete mode 100644 src/mloq/config/configuration.py delete mode 100644 src/mloq/config/custom_click.py delete mode 100644 src/mloq/config/param_patch.py delete mode 100644 src/mloq/config/prompt.py create mode 100644 src/mloq/core.py delete mode 100644 src/mloq/custom_click.py delete mode 100644 src/mloq/failure.py delete mode 100644 src/mloq/files.py delete mode 100644 src/mloq/git.py delete mode 100644 src/mloq/record.py delete mode 100644 src/mloq/runner.py delete mode 100644 src/mloq/templating.py delete mode 100644 src/mloq/writer.py delete mode 100644 tests/commands/__init__.py delete mode 100644 tests/commands/test_ci.py delete mode 100644 tests/commands/test_docker.py delete mode 100644 tests/commands/test_docs.py delete mode 100644 tests/commands/test_globals.py delete mode 100644 tests/commands/test_license.py delete mode 100644 tests/commands/test_lint.py delete mode 100644 tests/commands/test_package.py delete mode 100644 tests/commands/test_project.py delete mode 100644 tests/commands/test_setup.py delete mode 100644 tests/config/__init__.py delete mode 100644 tests/config/fixtures.py delete mode 100644 tests/config/test_configuration.py delete mode 100644 tests/config/test_param_patch.py delete mode 100644 tests/examples/__init__.py delete mode 100644 tests/examples/ci/test_1/mloq.yaml delete mode 100644 tests/examples/ci/test_1/target/.github/workflows/push.yml delete mode 100644 tests/examples/ci/test_1/target/WHAT_MLOQ_GENERATED.md delete mode 100644 tests/examples/ci/test_mloq/mloq.yaml delete mode 100644 tests/examples/ci/test_mloq/target/.github/workflows/push.yml delete mode 100644 tests/examples/ci/test_mloq/target/WHAT_MLOQ_GENERATED.md delete mode 100644 tests/examples/docker/test_1/mloq.yaml delete mode 100644 tests/examples/docker/test_1/target/Dockerfile delete mode 100644 tests/examples/docker/test_1/target/Makefile.docker delete mode 100644 tests/examples/docker/test_1/target/WHAT_MLOQ_GENERATED.md delete mode 100644 tests/examples/docker/test_cuda/mloq.yaml delete mode 100644 tests/examples/docker/test_cuda/target/Dockerfile delete mode 100644 tests/examples/docker/test_cuda/target/Makefile.docker delete mode 100644 tests/examples/docker/test_cuda/target/WHAT_MLOQ_GENERATED.md delete mode 100644 tests/examples/docker/test_mloq/mloq.yaml delete mode 100644 tests/examples/docker/test_mloq/target/Dockerfile delete mode 100644 tests/examples/docker/test_mloq/target/Makefile.docker delete mode 100644 tests/examples/docker/test_mloq/target/WHAT_MLOQ_GENERATED.md delete mode 100644 tests/examples/docs/test_1/mloq.yaml delete mode 100644 tests/examples/docs/test_1/target/WHAT_MLOQ_GENERATED.md delete mode 100644 tests/examples/docs/test_1/target/docs/Makefile delete mode 100644 tests/examples/docs/test_1/target/docs/make.bat delete mode 100644 tests/examples/docs/test_1/target/docs/requirements-docs.txt delete mode 100644 tests/examples/docs/test_1/target/docs/source/conf.py delete mode 100644 tests/examples/docs/test_1/target/docs/source/index.md delete mode 100644 tests/examples/license/test_1/mloq.yaml delete mode 100644 tests/examples/license/test_1/target/CODE_OF_CONDUCT.md delete mode 100644 tests/examples/license/test_1/target/CONTRIBUTING.md delete mode 100644 tests/examples/license/test_1/target/DCO.md delete mode 100644 tests/examples/license/test_1/target/LICENSE delete mode 100644 tests/examples/license/test_1/target/WHAT_MLOQ_GENERATED.md delete mode 100644 tests/examples/lint/test_1/mloq.yaml delete mode 100644 tests/examples/mloq.yaml delete mode 100644 tests/examples/package/test_1/mloq.yaml delete mode 100644 tests/examples/project/test_1/mloq.yaml delete mode 100644 tests/examples/project/test_mloq/mloq.yaml delete mode 100644 tests/examples/requirements/test_dogfood/mloq.yaml delete mode 100644 tests/examples/requirements/test_dogfood/target/requirements.txt delete mode 100644 tests/examples/requirements/test_ds_tf/mloq.yaml delete mode 100644 tests/examples/requirements/test_ds_tf/target/requirements.txt delete mode 100644 tests/examples/setup/test_mloq/target/.codecov.yml delete mode 100644 tests/examples/setup/test_mloq/target/.github/workflows/push.yml delete mode 100644 tests/examples/setup/test_mloq/target/.gitignore delete mode 100644 tests/examples/setup/test_mloq/target/.pre-commit-config.yaml delete mode 100644 tests/examples/setup/test_mloq/target/CODE_OF_CONDUCT.md delete mode 100644 tests/examples/setup/test_mloq/target/CONTRIBUTING.md delete mode 100644 tests/examples/setup/test_mloq/target/DCO.md delete mode 100644 tests/examples/setup/test_mloq/target/Dockerfile delete mode 100644 tests/examples/setup/test_mloq/target/LICENSE delete mode 100644 tests/examples/setup/test_mloq/target/Makefile.docker delete mode 100644 tests/examples/setup/test_mloq/target/README.md delete mode 100644 tests/examples/setup/test_mloq/target/WHAT_MLOQ_GENERATED.md delete mode 100644 tests/examples/setup/test_mloq/target/docs/Makefile delete mode 100644 tests/examples/setup/test_mloq/target/docs/make.bat delete mode 100644 tests/examples/setup/test_mloq/target/docs/requirements-docs.txt delete mode 100644 tests/examples/setup/test_mloq/target/docs/source/conf.py delete mode 100644 tests/examples/setup/test_mloq/target/docs/source/index.md delete mode 100644 tests/examples/setup/test_mloq/target/pyproject.toml delete mode 100644 tests/examples/setup/test_mloq/target/requirements-lint.txt delete mode 100644 tests/examples/setup/test_mloq/target/requirements-test.txt delete mode 100644 tests/examples/setup/test_mloq/target/requirements.txt delete mode 100644 tests/examples/setup/test_mloq/target/setup.py create mode 100644 tests/test_cli.py delete mode 100644 tests/test_command.py create mode 100644 tests/test_core.py delete mode 100644 tests/test_record.py delete mode 100644 tests/test_runner.py delete mode 100644 tests/test_writer.py diff --git a/._pre-commit-config.yaml b/._pre-commit-config.yaml new file mode 100644 index 00000000..0da324c5 --- /dev/null +++ b/._pre-commit-config.yaml @@ -0,0 +1,19 @@ +# To install the git pre-commit hooks run: +# pre-commit install --install-hooks +# To update the versions: +# pre-commit autoupdate +exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg)(/|$)' +# Note the order is intentional to avoid multiple passes of the hooks +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: main + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix, --show-fixes] + - id: ruff-format + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: main + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: debug-statements diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 00000000..b2ebcc80 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,29 @@ +[bumpversion] +current_version = 0.1.0 +;commit = True +;tag = True +; +; +; +;[bumpversion:file:docs/_config.yml] +; +; +;[bumpversion:file:src/mloq/version.py] +; +;search = __version__ = "{current_version}" +;replace = __version__ = "{new_version}" +; +; +;[bumpversion:file:.cookiecutterrc] +;search = version: "{current_version}" +;replace = version: "{new_version}" +; +; +;[bumpversion:file (badge):README.md] +;search = /v{current_version}.svg +;replace = /v{new_version}.svg +; +;[bumpversion:file (link):README.md] +;search = /v{current_version}...main +;replace = /v{new_version}...main +; diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index c72c1b4a..00000000 --- a/.codecov.yml +++ /dev/null @@ -1,19 +0,0 @@ -comment: - layout: "reach, diff, flags, files" - behavior: default - require_changes: true # if true: only post the comment if coverage changes - -github_checks: - annotations: true - -ignore: -- tests -coverage: - status: - project: - default: - target: auto - threshold: 0% - informational: true - paths: - - src/mloq diff --git a/.cookiecutterrc b/.cookiecutterrc new file mode 100644 index 00000000..5ddd3ea3 --- /dev/null +++ b/.cookiecutterrc @@ -0,0 +1,66 @@ +# This file exists so you can easily regenerate your project. +# +# `cookiepatcher` is a convenient shim around `cookiecutter` +# for regenerating projects (it will generate a .cookiecutterrc +# automatically for any template). To use it: +# +# pip install cookiepatcher +# cookiepatcher gh:ionelmc/cookiecutter-pylibrary ml-ops-quickstart +# +# See: +# https://pypi.org/project/cookiepatcher +# +# Alternatively, you can run: +# +# cookiecutter --overwrite-if-exists --config-file=ml-ops-quickstart/.cookiecutterrc gh:ionelmc/cookiecutter-pylibrary + +default_context: + c_extension_support: "no" + codacy: "no" + codacy_projectid: "[Get ID from https://app.codacy.com/gh/FragileTech/ml-ops-quickstart/settings]" + codeclimate: "no" + codecov: "yes" + command_line_interface: "click" + command_line_interface_bin_name: "mloq" + coveralls: "no" + distribution_name: "mloq" + docstring_code_line_length: "99" + email: "guillem@fragile.tech" + formatter_quote_style: "double" + full_name: "Guillem Duran Ballester" + function_name: "compute" + github_actions: "yes" + github_actions_osx: "yes" + github_actions_windows: "yes" + license: "MIT license" + line_length: "99" + lock_file_support: false + module_name: "core" + package_name: "mloq" + pre_commit: "yes" + project_name: "ML Ops Quickstart" + project_short_description: "Automate project creation following ML best practices." + project_slug: "ml-ops-quickstart" + pypi_badge: "yes" + pypi_disable_upload: "no" + release_date: "today" + repo_hosting: "github.com" + repo_hosting_domain: "github.com" + repo_main_branch: "main" + repo_name: "ml-ops-quickstart" + repo_url: "https://github.com/FragileTech/ml-ops-quickstart" + repo_username: "FragileTech" + scrutinizer: "no" + setup_py_uses_setuptools_scm: "no" + sphinx_docs: "yes" + sphinx_docs_hosting: "https://ml-ops-quickstart.readthedocs.io/" + sphinx_doctest: "no" + sphinx_theme: "furo" + target_python_version: "3.10" + test_matrix_separate_coverage: "no" + tests_inside_package: "no" + version: "0.1.0" + version_manager: "bump2version" + website: "fragile.tech" + year_from: "2024" + year_to: "2024" diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..84ca893f --- /dev/null +++ b/.coveragerc @@ -0,0 +1,16 @@ +[paths] +source = + src + */site-packages + +[run] +branch = true +source = + mloq + tests +parallel = true + +[report] +show_missing = true +precision = 2 +omit = *migrations* diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..586c7367 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# see https://editorconfig.org/ +root = true + +[*] +# Use Unix-style newlines for most files (except Windows files, see below). +end_of_line = lf +trim_trailing_whitespace = true +indent_style = space +insert_final_newline = true +indent_size = 4 +charset = utf-8 + +[*.{bat,cmd,ps1}] +end_of_line = crlf + +[*.{yml,yaml}] +indent_size = 2 + +[*.tsv] +indent_style = tab diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a2e8025f..29c9ecfe 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,88 +1,21 @@ -version: 2 -updates: -- package-ecosystem: pip - directory: "/" - schedule: - interval: weekly - time: "04:00" - open-pull-requests-limit: 10 - ignore: - - dependency-name: sphinx-autoapi - versions: - - 1.8.0 - - dependency-name: myst-parser - versions: - - 0.13.4 - - 0.13.6 - - dependency-name: pre-commit - versions: - - 2.11.0 - - 2.11.1 - - 2.12.0 - - 2.12.1 - - dependency-name: sphinx - versions: - - 3.5.0 - - 3.5.2 - - 3.5.3 - - 3.5.4 - - dependency-name: hypothesis - versions: - - 6.1.1 - - 6.2.0 - - 6.3.0 - - 6.3.4 - - 6.6.0 - - 6.8.1 - - 6.8.3 - - 6.8.4 - - 6.9.0 - - dependency-name: pylint - versions: - - 2.7.0 - - 2.7.2 - - 2.7.4 - - dependency-name: flake8-bugbear - versions: - - 20.11.1 - - 21.3.1 - - 21.3.2 - - 21.4.3 - - dependency-name: isort - versions: - - 5.7.0 - - 5.8.0 - - dependency-name: flake8-docstrings - versions: - - 1.6.0 - - dependency-name: flake8 - versions: - - 3.9.0 - - dependency-name: pycodestyle - versions: - - 2.7.0 - - dependency-name: flogging - versions: - - 0.0.12 - - 0.0.13 - - dependency-name: pytest-xdist - versions: - - 2.2.1 - - dependency-name: pytest - versions: - - 6.2.2 - - dependency-name: jinja2 - versions: - - 2.11.3 - - dependency-name: ruyaml - versions: - - 0.20.0 - - dependency-name: black - versions: - - 20.8b1 - - dependency-name: pytest-cov - versions: - - 2.11.1 - - dependency-name: flakehell - versions: - - 0.9.0 +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + - package-ecosystem: pip + directory: "/.github/workflows" + schedule: + interval: weekly + - package-ecosystem: pip + directory: "/docs" + schedule: + interval: weekly + - package-ecosystem: pip + directory: "/" + schedule: + interval: weekly + versioning-strategy: lockfile-only + allow: + - dependency-type: "all" diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 00000000..3a300b6c --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,66 @@ +--- +# Labels names are important as they are used by Release Drafter to decide +# regarding where to record them in changelog or if to skip them. +# +# The repository labels will be automatically configured using this file and +# the GitHub Action https://github.com/marketplace/actions/github-labeler. +- name: breaking + description: Breaking Changes + color: bfd4f2 +- name: bug + description: Something isn't working + color: d73a4a +- name: build + description: Build System and Dependencies + color: bfdadc +- name: ci + description: Continuous Integration + color: 4a97d6 +- name: dependencies + description: Pull requests that update a dependency file + color: 0366d6 +- name: documentation + description: Improvements or additions to documentation + color: 0075ca +- name: duplicate + description: This issue or pull request already exists + color: cfd3d7 +- name: enhancement + description: New feature or request + color: a2eeef +- name: github_actions + description: Pull requests that update Github_actions code + color: "000000" +- name: good first issue + description: Good for newcomers + color: 7057ff +- name: help wanted + description: Extra attention is needed + color: 008672 +- name: invalid + description: This doesn't seem right + color: e4e669 +- name: performance + description: Performance + color: "016175" +- name: python + description: Pull requests that update Python code + color: 2b67c6 +- name: question + description: Further information is requested + color: d876e3 +- name: refactoring + description: Refactoring + color: ef67c4 +- name: removal + description: Removals and Deprecations + color: 9ae7ea +- name: style + description: Style + color: c120e5 +- name: testing + description: Testing + color: b1fc6f +- name: wontfix + description: This will not be worked on + color: ffffff diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..bf4fae59 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,42 @@ +name-template: '$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' + +categories: + - title: '🚀 Features' + labels: + - 'feat' + - 'enhancement' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bug' + - title: 'Other updates' + labels: + - 'refactor' + - 'chore' + - 'docs' + - 'perf' + - 'test' + - 'documentation' + +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. + +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch + +template: | + ## Changes + + $CHANGES + + Special thanks to: $CONTRIBUTORS diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..08119c3a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,280 @@ +name: Build + +on: + push: + branches: + - main + pull_request: + branches: + - main + +env: + PROJECT_NAME: mloq + PROJECT_DIR: src/mloq + VERSION_FILE: src/mloq/version.py + DEFAULT_BRANCH: main + BOT_NAME: fragile-bot + BOT_EMAIL: bot@fragile.tech + DOCKER_ORG: fragiletech +# LOCAL_CACHE: | +# ~/.local/bin +# ~/.local/lib/python3.*/site-packages +# /opt/homebrew + + +jobs: + style-check: + name: Style check + if: "!contains(github.event.head_commit.message, 'Bump version')" + runs-on: ubuntu-latest + steps: + - name: actions/checkout + uses: actions/checkout@v4 + - name: Setup Rye + id: setup-rye + uses: eifinger/setup-rye@v4 + with: + enable-cache: true + cache-prefix: ubuntu-20.04-rye-check-${{ hashFiles('pyproject.toml') }} + - name: Run style check and linter + run: | + set -x + rye fmt --check + rye lint + pytest-rye: + name: Run pytest and coverage with Rye + if: "!contains(github.event.head_commit.message, 'Bump version')" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["ubuntu-latest", "macos-latest"] + python-version: ['3.10'] + + steps: + - uses: actions/checkout@v4 + + - name: Setup Rye + id: setup-rye + uses: eifinger/setup-rye@v4 + with: + enable-cache: true + cache-prefix: ${{ matrix.os }}-latest-rye-test-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} + +# - name: actions/cache +# uses: actions/cache@v4 +# with: +# path: ${{ env.LOCAL_CACHE }} +# key: ${{ matrix.os }}-latest-rye-test-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} +# restore-keys: ${{ matrix.os }}-latest-rye-test-${{ matrix.python-version }} + + - name: Install Ubuntu test and package dependencies + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + set -x + rye pin --relaxed cpython@${{ matrix.python-version }} + rye sync --all-features + + - name: Install MacOS test and package dependencies + if: ${{ matrix.os == 'macos-latest' }} + run: | + set -x + rye pin --relaxed cpython@${{ matrix.python-version }} + rye sync --all-features + + - name: Run Pytest on MacOS + if: ${{ matrix.os == 'macos-latest' }} + run: | + set -x + rye run test + + - name: Run code coverage on Ubuntu + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + set -x + rye run cov + + - name: Upload coverage report + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + if: ${{ matrix.python-version == '3.10' && matrix.os == 'ubuntu-latest' && env.CODECOV_TOKEN != '' }} + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: false # optional (default = false) + files: ./coverage.xml + flags: unittests # optional + name: codecov-umbrella # optional + token: ${{ secrets.CODECOV_TOKEN }} # required + verbose: true # optional (default = false) + + build-test-package: + name: Build and test the package + needs: style-check + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'Bump version')" + permissions: + contents: write + id-token: write + steps: + - name: actions/checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 100 + - name: Set Git user + run: | + git config --global user.name "${{ env.BOT_NAME }}" + git config --global user.email "${{ env.BOT_EMAIL }}" + + - name: Setup Rye + id: setup-rye + uses: eifinger/setup-rye@v4 + with: + enable-cache: true + cache-prefix: ubuntu-latest-rye-build-3.10-${{ hashFiles('pyproject.toml') }} +# - name: actions/cache +# uses: actions/cache@v4 +# with: +# path: ${{ env.LOCAL_CACHE }} +# key: ubuntu-latest-system-build-3.10-${{ hashFiles('pyproject.toml') }} +# restore-keys: ubuntu-latest-system-test + - name: Install build dependencies + run: | + set -x + rye pin --relaxed cpython@3.10 + rye install bump2version + rye install twine + + - name: Create unique version for test.pypi + run: | + set -x + current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2) + ts=$(date +%s) + new_version="$current_version$ts" + bumpversion --current-version $current_version --new-version $new_version patch $VERSION_FILE + + - name: Build package + run: | + set -x + rye build --clean + twine check dist/* + + - name: Publish 📦 to Test PyPI + env: + TEST_PYPI_PASS: ${{ secrets.TEST_PYPI_PASS }} + if: "'$TEST_PYPI_PASS' != ''" + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_PASS }} + repository-url: https://test.pypi.org/legacy/ + skip-existing: true + + bump-version: + name: Bump package version + env: + BOT_AUTH_TOKEN: ${{ secrets.BOT_AUTH_TOKEN }} + if: "!contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/main' && '$BOT_AUTH_TOKEN' != ''" + runs-on: ubuntu-latest + needs: + - pytest-rye + - build-test-package + steps: + - name: actions/checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 100 + - name: current_version + run: | + set -x + echo "current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2)" >> $GITHUB_ENV + echo "version_file=$VERSION_FILE" >> $GITHUB_ENV + echo 'bot_name="${BOT_NAME}"' >> $GITHUB_ENV + echo 'bot_email="${BOT_EMAIL}"' >> $GITHUB_ENV + - name: FragileTech/bump-version + uses: FragileTech/bump-version@main + with: + current_version: "${{ env.current_version }}" + files: "${{ env.version_file }}" + commit_name: "${{ env.bot_name }}" + commit_email: "${{ env.bot_email }}" + login: "${{ env.bot_name }}" + token: "${{ secrets.BOT_AUTH_TOKEN }}" + + release-package: + name: Release PyPI package + env: + PYPI_PASS: ${{ secrets.PYPI_PASS }} + if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/main' && '$PYPI_PASS' != ''" + permissions: + contents: write + id-token: write + runs-on: ubuntu-latest + steps: + - name: actions/checkout + uses: actions/checkout@v4 + - name: Setup Rye + id: setup-rye + uses: eifinger/setup-rye@v4 + with: + enable-cache: true + cache-prefix: ubuntu-latest-rye-release-3.10-${{ hashFiles('pyproject.toml') }} + - name: Install dependencies + run: | + set -x + rye pin --relaxed cpython@3.10 + rye install twine + + - name: Build package + run: | + set -x + rye build --clean + twine check dist/* + + - name: Publish 📦 to PyPI + env: + PYPI_PASS: ${{ secrets.PYPI_PASS }} + if: "'$PYPI_PASS' != ''" + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_PASS }} + skip-existing: true +# +# release: +# runs-on: ubuntu-latest +# environment: release +# needs: test +# if: startsWith(github.ref, 'refs/tags/') +# permissions: +# contents: write +# id-token: write +# +# steps: +# - uses: actions/checkout@v4 +# - name: Set up Python ${{ matrix.python-version }} +# uses: actions/setup-python@v5 +# with: +# python-version: '3.10' +# - name: Install dependencies +# shell: bash +# run: | +# python -m pip install --upgrade pip +# pip install hatch pre-commit +# - name: Build +# run: | +# hatch build +# - name: Publish 📦 to Test PyPI +# if: startsWith(github.ref, 'refs/heads/main') +# uses: pypa/gh-action-pypi-publish@release/v1 +# with: +# skip_existing: true +# user: __token__ +# password: ${{ secrets.TEST_PYPI_SECRECT }} +# packages-dir: dist/ +# repository-url: https://test.pypi.org/legacy/ +# - name: Publish 📦 to PyPI +# if: startsWith(github.ref, 'refs/heads/main') +# uses: pypa/gh-action-pypi-publish@release/v1 +# with: +# user: __token__ +# password: ${{ secrets.PYPI_SECRECT }} +# packages-dir: dist/ diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml deleted file mode 100644 index f3bc2fe7..00000000 --- a/.github/workflows/deploy-docs.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Pages -on: - push: - branches: - - master - -env: - NOTEBOOKS_SRC_DIR: "../notebooks" - NOTEBOOKS_BUILD_DIR: "./source/notebooks" - PIP_CACHE: | - ~/.cache/pip - ~/.local/bin - ~/.local/lib/python3.*/site-packages - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/setup-python@v2 - with: - python-version: "3.8" - - uses: actions/checkout@master - with: - fetch-depth: 0 # otherwise, you will failed to push refs to dest repo - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-docs-${{ hashFiles('requirements.txt') }} - restore-keys: ubuntu-20.04-pip-docs- - - name: Install package and dependencies - run: | - set -x - pip install -r requirements.txt - pip install . - if [ -e "${NOTEBOOKS_SRC_DIR}" ] && [ -e "${NOTEBOOKS_BUILD_DIR}" ]; then \ - echo "${NOTEBOOKS_BUILD_DIR} Updating notebook folder."; \ - rm -rf "${NOTEBOOKS_BUILD_DIR}"; \ - cp -r "${NOTEBOOKS_SRC_DIR}" "${NOTEBOOKS_BUILD_DIR}"; \ - fi - - name: Build and Commit - uses: sphinx-notes/pages@v2 - with: - documentation_path: ./docs/source - requirements_path: ./docs/requirements-docs.txt - - name: Push changes - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: gh-pages \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..2d9fc1bb --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,56 @@ +name: Build documentation + +on: + push: + branches: + - main + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment +concurrency: + group: "pages" + cancel-in-progress: true + +# Default to bash +defaults: + run: + shell: bash + +jobs: + build: + if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/main'" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install hatch pre-commit + hatch env create docs + - name: Build + run: hatch run docs:build-check + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./docs/_build/html + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/draft.yml b/.github/workflows/draft.yml new file mode 100644 index 00000000..a3df8718 --- /dev/null +++ b/.github/workflows/draft.yml @@ -0,0 +1,19 @@ +name: Release Drafter + +on: + push: + branches: + - main + +jobs: + update-draft: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + # Drafts your next Release notes as Pull Requests are merged into "main" + - uses: release-drafter/release-drafter@v6 + with: + disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 00000000..bd8ae32a --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,24 @@ +name: Labeler + +on: + push: + branches: + - main + +permissions: + actions: read + contents: read + security-events: write + pull-requests: write + +jobs: + labeler: + runs-on: ubuntu-latest + steps: + - name: Check out the repository + uses: actions/checkout@v4 + + - name: Run Labeler + uses: crazy-max/ghaction-github-labeler@v5.0.0 + with: + skip-delete: true diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index 2f1f7669..00000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,299 +0,0 @@ -name: Push - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - PROJECT_NAME: mloq - PROJECT_DIR: src/mloq - VERSION_FILE: src/mloq/version.py - DEFAULT_BRANCH: master - BOT_NAME: fragile-bot - BOT_EMAIL: bot@fragile.tech - DOCKER_ORG: fragiletech - PIP_CACHE: | - ~/.cache/pip - ~/.local/bin - ~/.local/lib/python3.*/site-packages - -jobs: - style-check: - name: Style check - if: "!contains(github.event.head_commit.message, 'Bump version')" - runs-on: ubuntu-20.04 - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: "3.8" - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-lint-${{ hashFiles('requirements-lint.txt') }} - restore-keys: ubuntu-20.04-pip-lint- - - name: Install lint dependencies - run: | - set -x - pip install -r requirements-lint.txt - - - name: Run style check and linter - run: | - set -x - make check - - pytest: - name: Run Pytest - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - strategy: - matrix: - python-version: ['3.6', '3.7', '3.8', '3.9'] - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-test-${{ matrix.python-version }}-${{ hashFiles('requirements.txt', 'requirements-test.txt') }} - restore-keys: ubuntu-20.04-pip-test- - - name: Install test and package dependencies - run: | - set -x - pip install -r requirements-test.txt -r requirements.txt - pip install . - - name: Additional setup - run: | - set -x - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - #diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py - - - name: Test with pytest - run: | - set -x - make test-codecov - - - name: Upload coverage report - if: ${{ matrix.python-version=='3.8' }} - uses: codecov/codecov-action@v3 - - test-docker: - name: Test Docker container - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - uses: actions/checkout@v2 - - name: Build container - run: | - set -x - make docker-build - - name: Run tests - run: | - set -x - make docker-test - - build-test-package: - name: Build and test the package - needs: style-check - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-test-3.8-${{ hashFiles('requirements.txt', 'requirements-test.txt') }} - restore-keys: ubuntu-20.04-pip-test- - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel bump2version - - - name: Create unique version for test.pypi - run: | - set -x - current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2) - ts=$(date +%s) - new_version="$current_version$ts" - bumpversion --current-version $current_version --new-version $new_version patch $VERSION_FILE - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to TestPyPI - env: - TEST_PYPI_PASS: ${{ secrets.TEST_PYPI_PASS }} - if: "'$TEST_PYPI_PASS' != ''" - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.TEST_PYPI_PASS }} - repository_url: https://test.pypi.org/legacy/ - skip_existing: true - - - name: Install dependencies - run: | - set -x - python -m pip install dist/*.whl -r requirements-test.txt - - name: Additional setup - run: | - set -x - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - #diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py - - - name: Test package - run: | - set -x - rm -rf $PROJECT_DIR - make test - - bump-version: - name: Bump package version - env: - BOT_AUTH_TOKEN: ${{ secrets.BOT_AUTH_TOKEN }} - if: "!contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$BOT_AUTH_TOKEN' != ''" - runs-on: ubuntu-20.04 - needs: - - pytest - - build-test-package - - test-docker - steps: - - name: actions/checkout - uses: actions/checkout@v2 - with: - persist-credentials: false - fetch-depth: 100 - - name: current_version - run: | - set -x - echo "current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2)" >> $GITHUB_ENV - echo "version_file=$VERSION_FILE" >> $GITHUB_ENV - echo 'bot_name="${BOT_NAME}"' >> $GITHUB_ENV - echo 'bot_email="${BOT_EMAIL}"' >> $GITHUB_ENV - - name: FragileTech/bump-version - uses: FragileTech/bump-version@main - with: - current_version: "${{ env.current_version }}" - files: "${{ env.version_file }}" - commit_name: "${{ env.bot_name }}" - commit_email: "${{ env.bot_email }}" - login: "${{ env.bot_name }}" - token: "${{ secrets.BOT_AUTH_TOKEN }}" - - push-docker: - name: Push Docker container - runs-on: ubuntu-20.04 - env: - DOCKERHUB_PASS: ${{ secrets.DOCKERHUB_PASS }} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$DOCKERHUB_PASS' != ''" - steps: - - uses: actions/checkout@v2 - - name: Login to DockerHub - run: | - set -x - docker login -u "${{ secrets.DOCKERHUB_LOGIN }}" -p "${{ secrets.DOCKERHUB_PASS }}" docker.io - - - name: Build container - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-build VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG - - name: Push images - - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-push VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG - - release-package: - name: Release PyPI package - env: - PYPI_PASS: ${{ secrets.PYPI_PASS }} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$PYPI_PASS' != ''" - runs-on: ubuntu-20.04 - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.PYPI_PASS }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..9f082778 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,41 @@ +name: Tests + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + tests: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + python-version: ["3.9", "3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python_version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python_version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install hatch pre-commit + hatch env create + - name: Lint and typecheck + run: | + hatch run lint:all + - name: Run Tests + run: | + hatch run test:test diff --git a/.gitignore b/.gitignore index 3e64b337..77973dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,131 +1,74 @@ -# This file is here just for the reference -WHAT_MLOQ_GENERATED.md - -#Mac OS -*.DS_Store - -#PyCharm IDE -.idea/ - -# Documentation build templates -docs/_build/ -docs/build/ - -# Byte-compiled / optimized / DLL templates -**/__pycache__/ *.py[cod] -*$py.class -*.pyc +__pycache__ + +# Temp files +.*.sw[po] +*~ +*.bak +.DS_Store # C extensions *.so -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata -*.egg-info/ -.installed.cfg +# Build and package files *.egg - -# PyInstaller -# Usually these templates are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec +*.egg-info +.bootstrap +.build +.cache +.eggs +.env +.installed.cfg +.ve +bin +build +develop-eggs +dist +eggs +lib +lib64 +parts +pip-wheel-metadata/ +pyvenv*/ +sdist +var +venv*/ +wheelhouse # Installer logs pip-log.txt -pip-delete-this-directory.txt # Unit test / coverage reports -htmlcov/ -.tox/ +.benchmarks .coverage .coverage.* -.cache -nosetests.xml +.pytest +.pytest_cache/ +.tox coverage.xml -*.cover -.hypothesis/ +htmlcov +nosetests.xml # Translations *.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache -# Scrapy stuff: -.scrapy +# Buildout +.mr.developer.cfg -# Sphinx documentation -docsrc/_build/ +# IDE project files +*.iml +*.komodoproject +.idea +.project +.pydevproject +.vscode -# PyBuilder -target/ +# Complexity +output/*.html +output/*/index.html -# Jupyter Notebook -.ipynb_checkpoints +# Sphinx +docs/_build -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed templates -*.sage.py - -# dotenv -.env - -# virtualenv -.venv -venv/ -ENV/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy +# Mypy Cache .mypy_cache/ - -# CI -.ci - -# Notebooks by default are ignored -*.ipynb - -# lock files -*.lock - -# hydra command history -outputs/ - -*.pck -*.npy diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 98e5a947..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,10 +0,0 @@ -repos: -- repo: https://github.com/psf/black - rev: 20.8b1 - hooks: - - id: black -- repo: https://github.com/PyCQA/isort - rev: 5.8.0 - hooks: - - id: isort - args: ["."] diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..009a913c --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,14 @@ +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +version: 2 +sphinx: + configuration: docs/conf.py +formats: all +build: + os: ubuntu-22.04 + tools: + python: "3" +python: + install: + - requirements: docs/requirements.txt + - method: pip + path: . diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 00000000..ae7b5f66 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,5 @@ + +Authors +======= + +* Guillem Duran Ballester - fragile.tech diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..a54242ab --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,8 @@ + +Changelog +========= + +0.1.0 (2024-10-02) +------------------ + +* First release on PyPI. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 6b10d223..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ - -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at guillem@fragile.tech. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available -at [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index ab70dd9c..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,56 +0,0 @@ -# Contributing Guidelines - -mloq is [MIT licensed](LICENSE) and accepts -contributions via GitHub pull requests. This document outlines some of the -conventions on development workflow, commit message formatting, contact points, -and other resources to make it easier to get your contribution accepted. - -## Certificate of Origin - -By contributing to this project you agree to the [Developer Certificate of -Origin (DCO)](DCO.md). This document was created by the Linux Kernel community and is a -simple statement that you, as a contributor, have the legal right to make the -contribution. - -In order to show your agreement with the DCO you should include at the end of commit message, -the following line: `Signed-off-by: John Doe `, using your real name. - -This can be done easily using the [`-s`](https://github.com/git/git/blob/b2c150d3aa82f6583b9aadfecc5f8fa1c74aca09/Documentation/git-commit.txt#L154-L161) flag on the `git commit`. - - -## Support Channels - -The official support channels, for both users and contributors, are: - -- GitHub [issues](https://github.com/FragileTech/ml-ops-quickstart/issues)* - -*Before opening a new issue or submitting a new pull request, it's helpful to -search the project - it's likely that another user has already reported the -issue you're facing, or it's a known issue that we're already aware of. - - -## How to Contribute - -Pull Requests (PRs) are the main and exclusive way to contribute to the official project. -In order for a PR to be accepted it needs to pass a list of requirements: - -- The CI style check passes (run locally with `make check`). -- Code Coverage does not decrease. -- All the tests pass. -- Python code is formatted according to [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/). -- If the PR is a bug fix, it has to include a new unit test that fails before the patch is merged. -- If the PR is a new feature, it has to come with a suite of unit tests, that tests the new functionality. -- In any case, all the PRs have to pass the personal evaluation of at least one of the [maintainers](MAINTAINERS.md). - - -### Format of the commit message - -The commit summary must start with a capital letter and with a verb in present tense. No dot in the end. - -``` -Add a feature -Remove unused code -Fix a bug -``` - -Every commit details should describe what was changed, under which context and, if applicable, the GitHub issue it relates to. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000..e4c8094a --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,85 @@ +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +Bug reports +=========== + +When `reporting a bug `_ please include: + + * Your operating system name and version. + * Any details about your local setup that might be helpful in troubleshooting. + * Detailed steps to reproduce the bug. + +Documentation improvements +========================== + +ML Ops Quickstart could always use more documentation, whether as part of the +official ML Ops Quickstart docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Feature requests and feedback +============================= + +The best way to send feedback is to file an issue at https://github.com/FragileTech/ml-ops-quickstart/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that code contributions are welcome :) + +Development +=========== + +To set up `ml-ops-quickstart` for local development: + +1. Fork `ml-ops-quickstart `_ + (look for the "Fork" button). +2. Clone your fork locally:: + + git clone git@github.com:YOURGITHUBNAME/ml-ops-quickstart.git + +3. Create a branch for local development:: + + git checkout -b name-of-your-bugfix-or-feature + + Now you can make your changes locally. + +4. When you're done making changes run all the checks and docs builder with one command:: + + tox + +5. Commit your changes and push your branch to GitHub:: + + git add . + git commit -m "Your detailed description of your changes." + git push origin name-of-your-bugfix-or-feature + +6. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +If you need some code review or feedback while you're developing the code just make the pull request. + +For merging, you should: + +1. Include passing tests (run ``hatch``). +2. Update documentation when there's new API, functionality etc. +3. Add a note to ``CHANGELOG.rst`` about the changes. +4. Add yourself to ``AUTHORS.rst``. + +Tips +---- + +To run a subset of tests:: + + tox -e envname -- pytest -k test_myfeature + +To run all the test environments in *parallel*:: + + tox -p auto diff --git a/DCO.md b/DCO.md deleted file mode 100644 index e440da92..00000000 --- a/DCO.md +++ /dev/null @@ -1,36 +0,0 @@ -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -660 York Street, Suite 102, -San Francisco, CA 94110 USA - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with -this project or the open source license(s) involved. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 215e00e9..00000000 --- a/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM ubuntu:20.04 -ARG JUPYTER_PASSWORD="mloq" -ENV BROWSER=/browser \ - LC_ALL=en_US.UTF-8 \ - LANG=en_US.UTF-8 -COPY Makefile.docker Makefile -COPY . mloq/ - -RUN apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends make cmake && \ - make install-python3.8 && \ - make install-common-dependencies && \ - make install-python-libs - -RUN cd mloq \ - && python3 -m pip install -U pip \ - && pip3 install -r requirements-lint.txt \ - && pip3 install -r requirements-test.txt \ - && pip3 install -r requirements.txt \ - && pip3 install ipython jupyter \ - && pip3 install -e . \ - && git config --global init.defaultBranch master \ - && git config --global user.name "Whoever" \ - && git config --global user.email "whoever@fragile.tech" - -RUN make remove-dev-packages - -RUN mkdir /root/.jupyter && \ - echo 'c.NotebookApp.token = "'${JUPYTER_PASSWORD}'"' > /root/.jupyter/jupyter_notebook_config.py -CMD jupyter notebook --allow-root --port 8080 --ip 0.0.0.0 diff --git a/LICENSE b/LICENSE index e8c6ea07..a5daa02f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,9 @@ MIT License -Copyright (c) 2020 - 2022 FragileTech +Copyright (c) 2024, Guillem Duran Ballester -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..d0dac9c3 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,22 @@ +graft docs +graft src +graft ci +graft tests + +include .bumpversion.cfg +include .cookiecutterrc +include .coveragerc +include .editorconfig +include .github/workflows/github-actions.yml +include .pre-commit-config.yaml +include .readthedocs.yml +include pytest.ini +include tox.ini + +include AUTHORS.rst +include CHANGELOG.rst +include CONTRIBUTING.rst +include LICENSE +include README.rst + +global-exclude *.py[cod] __pycache__/* *.so *.dylib diff --git a/Makefile b/Makefile deleted file mode 100644 index d7b6e67f..00000000 --- a/Makefile +++ /dev/null @@ -1,52 +0,0 @@ -current_dir = $(shell pwd) - -PROJECT = mloq -n ?= auto -DOCKER_ORG = fragiletech -DOCKER_TAG ?= ${PROJECT} -VERSION ?= latest - -.POSIX: -style: - black . - isort . - -.POSIX: -check: - !(grep -R /tmp tests) - flakehell lint src/${PROJECT} - pylint src/${PROJECT} - black --check src/${PROJECT} - -.PHONY: test -test: - find -name "*.pyc" -delete - pytest -n $n -s -o log_cli=true -o log_cli_level=info - -.PHONY: test-codecov -test-codecov: - find -name "*.pyc" -delete - pytest -n $n -s -o log_cli=true -o log_cli_level=info --cov=./src/mloq --cov-report=xml --cov-config=pyproject.toml - -.PHONY: docker-shell -docker-shell: - docker run --rm -v ${current_dir}:/${PROJECT} --network host -w /${PROJECT} -it ${DOCKER_ORG}/${PROJECT}:${VERSION} bash - -.PHONY: docker-notebook -docker-notebook: - docker run --rm -v ${current_dir}:/${PROJECT} --network host -w /${PROJECT} -it ${DOCKER_ORG}/${PROJECT}:${VERSION} - -.PHONY: docker-build -docker-build: - docker build --pull -t ${DOCKER_ORG}/${PROJECT}:${VERSION} . - -.PHONY: docker-test -docker-test: - find -name "*.pyc" -delete - docker run --rm --network host -w /${PROJECT} --entrypoint python3 ${DOCKER_ORG}/${PROJECT}:${VERSION} -m pytest -n $n -s -o log_cli=true -o log_cli_level=info - -.PHONY: docker-push -docker-push: - docker push ${DOCKER_ORG}/${DOCKER_TAG}:${VERSION} - docker tag ${DOCKER_ORG}/${DOCKER_TAG}:${VERSION} ${DOCKER_ORG}/${DOCKER_TAG}:latest - docker push ${DOCKER_ORG}/${DOCKER_TAG}:latest diff --git a/Makefile.docker b/Makefile.docker deleted file mode 100644 index 67b30b5a..00000000 --- a/Makefile.docker +++ /dev/null @@ -1,79 +0,0 @@ -current_dir = $(shell pwd) - -PROJECT = dockerfiles -VERSION ?= latest -DOCKER_TAG = None -PYTHON_VERSION = "3.8" -UBUNTU_NAME = $(lsb_release -s -c) - -# Install system packages -.PHONY: install-common-dependencies -install-common-dependencies: - apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends \ - ca-certificates locales pkg-config apt-utils gcc g++ wget make cmake git curl flex ssh gpgv \ - libffi-dev libjpeg-turbo-progs libjpeg8-dev libjpeg-turbo8 libjpeg-turbo8-dev gnupg2 \ - libpng-dev libpng16-16 libglib2.0-0 bison gfortran lsb-release \ - libsm6 libxext6 libxrender1 libfontconfig1 libhdf5-dev libopenblas-base libopenblas-dev \ - libfreetype6 libfreetype6-dev zlib1g-dev zlib1g xvfb python-opengl ffmpeg libhdf5-dev && \ - ln -s /usr/lib/x86_64-linux-gnu/libz.so /lib/ && \ - ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /lib/ && \ - echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ - locale-gen && \ - wget -O - https://bootstrap.pypa.io/get-pip.py | python3 && \ - rm -rf /var/lib/apt/lists/* && \ - echo '#!/bin/bash\n\\n\echo\n\echo " $@"\n\echo\n\' > /browser && \ - chmod +x /browser - - -.PHONY: remove-dev-packages -remove-dev-packages: - pip3 uninstall -y cython && \ - apt-get remove -y cmake pkg-config flex bison curl libpng-dev \ - libjpeg-turbo8-dev zlib1g-dev libhdf5-dev libopenblas-dev gfortran \ - libfreetype6-dev libjpeg8-dev libffi-dev && \ - apt-get autoremove -y && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Install Python 3.9 -.PHONY: install-python3.9 -install-python3.9: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.9 python3.9-dev python3-distutils python3-setuptools - -# Install Python 3.8 -.PHONY: install-python3.8 -install-python3.8: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.8 python3.8-dev python3-distutils python3-setuptools - -# Install Python 3.7 -.PHONY: install-python3.7 -install-python3.7: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.7 python3.7-dev python3-distutils python3-setuptools - -# Install Python 3.6 -.PHONY: install-python3.6 -install-python3.6: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.6 python3.6-dev python3-distutils python3-setuptools \ - -# Install phantomjs for holoviews image save -.PHONY: install-phantomjs -install-phantomjs: - curl -sSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ - echo "deb https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee /etc/apt/sources.list.d/nodesource.list && \ - echo "deb-src https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee -a /etc/apt/sources.list.d/nodesource.list && \ - apt-get update && apt-get install -y nodejs && \ - npm install phantomjs --unsafe-perm && \ - npm install -g phantomjs-prebuilt --unsafe-perm - -# Install common python dependencies -.PHONY: install-python-libs -install-python-libs: - python3 -m pip install -U pip && \ - pip3 install --no-cache-dir setuptools wheel cython pipenv && \ - pip3 install --no-cache-dir matplotlib && \ - python3 -c "import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot" diff --git a/README.md b/README.md index ce4dbcfa..22a60844 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,97 @@ -# ML Ops Quickstart -[![Documentation Status](https://readthedocs.org/projects/mloq/badge/?version=latest)](https://mloq.readthedocs.io/en/latest/?badge=latest) -[![Code coverage](https://codecov.io/github/fragiletech/ml-ops-quickstart/coverage.svg)](https://codecov.io/github/fragiletech/ml-ops-quickstart) -[![PyPI package](https://badgen.net/pypi/v/mloq)](https://pypi.org/project/mloq/) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) -[![license: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) +======== +Overview +======== -ML Ops Quickstart is a tool for initializing Machine Learning projects following ML Ops best practices. +.. start-badges -Setting up new repositories is a time-consuming task that involves creating different files and -configuring tools such as linters, docker containers and continuous integration pipelines. -The goal of `mloq` is to simplify that process, so you can start writing code as fast as possible. +.. list-table:: + :stub-columns: 1 -`mloq` generates customized templates for Python projects with focus on Maching Learning. An example of -the generated templates can be found in [mloq-template](https://github.com/FragileTech/mloq-template). + * - docs + - |docs| + * - tests + - |github-actions| |codecov| + * - package + - |version| |wheel| |supported-versions| |supported-implementations| |commits-since| +.. |docs| image:: https://readthedocs.org/projects/ml-ops-quickstart/badge/?style=flat + :target: https://readthedocs.org/projects/ml-ops-quickstart/ + :alt: Documentation Status -## [1.](#Index) Installation +.. |github-actions| image:: https://github.com/FragileTech/ml-ops-quickstart/actions/workflows/github-actions.yml/badge.svg + :alt: GitHub Actions Build Status + :target: https://github.com/FragileTech/ml-ops-quickstart/actions -`mloq` is tested on Ubuntu 18.04+, and supports Python 3.6+. +.. |codecov| image:: https://codecov.io/gh/FragileTech/ml-ops-quickstart/branch/main/graphs/badge.svg?branch=main + :alt: Coverage Status + :target: https://app.codecov.io/github/FragileTech/ml-ops-quickstart -### Install from pypi -```bash -pip install mloq -``` -### Install from source -```bash -git clone https://github.com/FragileTech/ml-ops-quickstart.git -cd ml-ops-quickstart -pip install -e . -``` +.. |version| image:: https://img.shields.io/pypi/v/mloq.svg + :alt: PyPI Package latest release + :target: https://pypi.org/project/mloq -## [2.](#Index) Usage -### [2.1](#Index) Command line interface +.. |wheel| image:: https://img.shields.io/pypi/wheel/mloq.svg + :alt: PyPI Wheel + :target: https://pypi.org/project/mloq -Options: -* `--file` `-f`: Name of the configuration file. If `file` it's a directory it will load the `mloq.yml` file present in it. +.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/mloq.svg + :alt: Supported versions + :target: https://pypi.org/project/mloq -* `--overwrite` `-o`: Rewrite files that already exist in the target project. -* `--interactive` `-i`: Missing configuration data can be defined interactively from the CLI. +.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/mloq.svg + :alt: Supported implementations + :target: https://pypi.org/project/mloq -#### Usage examples -Arguments: -* `OUTPUT_DIRECTORY`: Path to the target project. +.. |commits-since| image:: https://img.shields.io/github/commits-since/FragileTech/ml-ops-quickstart/v0.1.0.svg + :alt: Commits since latest release + :target: https://github.com/FragileTech/ml-ops-quickstart/compare/v0.1.0...main -To set up a new repository from scratch interactively in the curren working directory: -```bash -mloq setup -i . -``` -To load a `mloq.yml` configuration file from the current repository, and initialize the directory `example`, and -overwrite all existing files with no interactivity: -```bash -mloq setup -f . -o example -``` -![ci python](docs/images/mloq_setup.png) +.. end-badges -## [5.](#Index) License -ML Ops Quickstart is released under the [MIT](LICENSE) license. +Automate project creation following ML best practices. -## [6.](#Index) Contributing +* Free software: MIT license -Contributions are very welcome! Please check the [contributing guidelines](CONTRIBUTING.md) before opening a pull request. +Installation +============ + +:: + + pip install mloq + +You can also install the in-development version with:: + + pip install https://github.com/FragileTech/ml-ops-quickstart/archive/main.zip + + +Documentation +============= + + +https://ml-ops-quickstart.readthedocs.io/ + + +Development +=========== + +To run all the tests run:: + + tox + +Note, to combine the coverage data from all the tox environments run: + +.. list-table:: + :widths: 10 90 + :stub-columns: 1 + + - - Windows + - :: + + set PYTEST_ADDOPTS=--cov-append + tox + + - - Other + - :: + + PYTEST_ADDOPTS=--cov-append tox diff --git a/WHAT_MLOQ_GENERATED.md b/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index 488648d9..00000000 --- a/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,30 +0,0 @@ -# What mloq generated for your project - -* `.codecov.yml` - configuration of CodeCov service to track the code coverage -* `.gitignore` - list of files and directories ignored by Git operations -* `.pre-commit-config.yaml` - Git pre-commit hooks configuration -* `CODE_OF_CONDUCT.md` - behavioral rules and norms in open source projects -* `CONTRIBUTING.md` - technical manual on how to contrib to the open source project -* `DCO.md` - Developer Certificate of Origin - needed in open source projects to certify that the incoming contributions are legitimate -* `Dockerfile` - Docker container for the project -* `LICENSE` - license of the project -* `Makefile` - common make commands for building the documentation -* `Makefile` - common make commands for development -* `Makefile.docker` - Makefile for the Docker container setup -* `README.md` - README -* `__init__.py` - Python package header for the project module -* `__init__.py` - Python package header for the test module -* `__main__.py` - Python package executable entry point -* `conf.py` - configuration file for sphinx and doc plugins -* `deploy-docs.yml` - workflow for deploying the documentation to GitHub pages -* `index.md` - configuration file for sphinx and doc plugins -* `make.bat` - common make commands for building the documentation -* `push.yml` - GitHub Actions continuous integration workflow file -* `pyproject.toml` - configuration of various development tools: linters, formatters, packaging -* `requirements-docs.txt` - list of exact versions of the packages needed to build your documentation -* `requirements-lint.txt` - list of exact versions of the packages used to check your code style -* `requirements-test.txt` - list of exact versions of the packages needed to run your test suite -* `requirements.txt` - list of exact versions of the packages on which your project depends -* `setup.py` - Python package installation metadata -* `test_main.py` - Unit test of the python package executable entry point -* `version.py` - defines the version of the package that is incremented on each push diff --git a/__pytest.ini b/__pytest.ini new file mode 100644 index 00000000..ddbac936 --- /dev/null +++ b/__pytest.ini @@ -0,0 +1,30 @@ +[pytest] +# If a pytest section is found in one of the possible config files +# (pytest.ini, tox.ini or setup.cfg), then pytest will not look for any others, +# so if you add a pytest config section elsewhere, +# you will need to delete this section from setup.cfg. +norecursedirs = + migrations + +python_files = + test_*.py + *_test.py + tests.py +addopts = + -ra + --strict-markers + --doctest-modules + --doctest-glob=\*.rst + --tb=short +testpaths = + tests +# If you want to switch back to tests outside package just remove --pyargs +# and edit testpaths to have "tests/" instead of "mloq". + +# Idea from: https://til.simonwillison.net/pytest/treat-warnings-as-errors +filterwarnings = + error +# You can add exclusions, some examples: +# ignore:'mloq' defines default_app_config:PendingDeprecationWarning:: +# ignore:The {{% if::: +# ignore:Coverage disabled via --no-cov switch! diff --git a/_requirements.txt b/_requirements.txt deleted file mode 100644 index a026ea02..00000000 --- a/_requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -click==8.0.3 -flogging==0.0.14 -hydra-core==1.1.1 -invoke==1.6.0 -jinja2==3.0.3 -omegaconf==2.1.1 -param==1.12.0 -pre-commit==2.15.0 -typing-extensions==4.0.0 \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index a159fb71..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build -NOTEBOOKS_SRC_DIR = ../notebooks -NOTEBOOKS_BUILD_DIR = ./source/notebooks - - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: server -server: - python3 -m http.server --directory build/html/ - -# rm -rf source/notebooks -# cp -r ../notebooks ./source/notebooks - -.PHONY: test -test: - if [ -e "${NOTEBOOKS_SRC_DIR}" ] && [ -e "${NOTEBOOKS_BUILD_DIR}" ]; then \ - echo "${NOTEBOOKS_BUILD_DIR} Updating notebook folder."; \ - rm -rf "${NOTEBOOKS_BUILD_DIR}"; \ - cp -r "${NOTEBOOKS_SRC_DIR}" "${NOTEBOOKS_BUILD_DIR}"; \ - fi - make html - make server \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 00000000..65fb5615 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,114 @@ +####################################################################################### +# A default configuration that will be loaded for all jupyter books +# Users are expected to override these values in their own `_config.yml` file. +# This is also the "master list" of all allowed keys and values. + +####################################################################################### +# Book settings +title : "ML Ops Quickstart" # The title of the book. Will be placed in the left navbar. +author : "Guillem Duran Ballester" # The author of the book +copyright : "2024" # Copyright year to be placed in the footer +logo : "logo.png" # A path to the book logo +# Patterns to skip when building the book. Can be glob-style (e.g. "*skip.ipynb") +exclude_patterns : [_build, Thumbs.db, .DS_Store, "**.ipynb_checkpoints"] +# Auto-exclude files not in the toc +only_build_toc_files : false + +####################################################################################### +# Execution settings +execute: + execute_notebooks : auto # Whether to execute notebooks at build time. Must be one of ("auto", "force", "cache", "off") + cache : "" # A path to the jupyter cache that will be used to store execution artifacts. Defaults to `_build/.jupyter_cache/` + exclude_patterns : [] # A list of patterns to *skip* in execution (e.g. a notebook that takes a really long time) + timeout : 30 # The maximum time (in seconds) each notebook cell is allowed to run. + run_in_temp : false # If `True`, then a temporary directory will be created and used as the command working directory (cwd), + # otherwise the notebook's parent directory will be the cwd. + allow_errors : false # If `False`, when a code cell raises an error the execution is stopped, otherwise all cells are always run. + stderr_output : show # One of 'show', 'remove', 'remove-warn', 'warn', 'error', 'severe' + +####################################################################################### +# Parse and render settings +parse: + myst_enable_extensions: # default extensions to enable in the myst parser. See https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html + - amsmath + - colon_fence + - deflist + - dollarmath + - html_admonition + - html_image + - linkify + - replacements + - smartquotes + - substitution + - tasklist + myst_url_schemes: [mailto, http, https] # URI schemes that will be recognised as external URLs in Markdown links + myst_dmath_double_inline: true # Allow display math ($$) within an inline context + +####################################################################################### +# HTML-specific settings +html: + favicon : "favicon.png" # A path to a favicon image + use_edit_page_button : false # Whether to add an "edit this page" button to pages. If `true`, repository information in repository: must be filled in + use_repository_button : false # Whether to add a link to your repository button + use_issues_button : false # Whether to add an "open an issue" button + use_multitoc_numbering : true # Continuous numbering across parts/chapters + extra_footer : "" # Will be displayed underneath the footer. + google_analytics_id : "" # A GA id that can be used to track book views. + home_page_in_navbar : true # Whether to include your home page in the left Navigation Bar + baseurl : "" # The base URL where your book will be hosted. Used for creating image previews and social links. e.g.: https://mypage.com/mybook/ + analytics: + plausible_analytics_url: https://plausible.io/js/script.js + + comments: + hypothesis : false + utterances : false + announcement : "" # A banner announcement at the top of the site. + +####################################################################################### +# LaTeX-specific settings +latex: + latex_engine : pdflatex # one of 'pdflatex', 'xelatex' (recommended for unicode), 'luatex', 'platex', 'uplatex' + use_jupyterbook_latex : true # use sphinx-jupyterbook-latex for pdf builds as default + +####################################################################################### +# Launch button settings +launch_buttons: + notebook_interface : classic # The interface interactive links will activate ["classic", "jupyterlab"] + binderhub_url : "" # The URL of the BinderHub (e.g., https://mybinder.org) + jupyterhub_url : "" # The URL of the JupyterHub (e.g., https://datahub.berkeley.edu) + thebe : false # Add a thebe button to pages (requires the repository to run on Binder) + colab_url : "" # The URL of Google Colab (https://colab.research.google.com) + +repository: + url : "https://github.com/FragileTech/ml-ops-quickstart" # The URL to your book's repository + path_to_book : "" # A path to your book's folder, relative to the repository root. + branch : "main" # Which branch of the repository should be used when creating links + +####################################################################################### +####################################################################################### +# Advanced and power-user settings +sphinx: + extra_extensions : # A list of extra extensions to load by Sphinx (added to those already used by JB). + - sphinx.ext.autodoc + - sphinx.ext.doctest + - sphinx.ext.intersphinx + - sphinx.ext.todo + - sphinx.ext.coverage + - sphinx.ext.imgmath + - sphinx.ext.viewcode + - sphinx.ext.napoleon + - sphinx.ext.autosectionlabel + - sphinx.ext.autodoc.typehints + - sphinx.ext.githubpages + - sphinxcontrib.mermaid + - autoapi.extension + local_extensions : # A list of local extensions to load by sphinx specified by "name: path" items + recursive_update : false # A boolean indicating whether to overwrite the Sphinx config (true) or recursively update (false) + config : # key-value pairs to directly over-ride the Sphinx configuration + autoapi_dirs : [ "../src" ] + napoleon_google_docstring : true + napoleon_numpy_docstring : false + autodoc_typehints : "description" + autoapi_add_toctree_entry : true + version : 0.1.0 # The version of the book + release : 0.1.0 # The release of the book diff --git a/docs/_toc.yml b/docs/_toc.yml new file mode 100644 index 00000000..421aed2f --- /dev/null +++ b/docs/_toc.yml @@ -0,0 +1,9 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: index +chapters: +- file: markdown +- file: notebooks +- file: markdown-notebooks diff --git a/docs/favicon.png b/docs/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..adb0b33ccdc78feaec4d557f7d86eabce2dfc816 GIT binary patch literal 51011 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 diff --git a/docs/images/mloq_setup.png b/docs/images/mloq_setup.png deleted file mode 100644 index 475b40e67024519239a91bb717b1b1fb4b2ee799..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 447960 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 HSy4BC1DuaC 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 BtcCyp diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..2983e227 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,13 @@ +# Welcome to ML Ops Quickstart Jupyter Book + +This is the main documentation page for **ML Ops Quickstart**. + +This is a small sample book to give you a feel for how book content is +structured. +It shows off a few of the major file types, as well as some sample content. +It does not go in-depth into any particular topic - check out [the Jupyter Book documentation](https://jupyterbook.org) for more information. + +Check out the content pages bundled with this sample book to see more. + +```{tableofcontents} +``` diff --git a/docs/logo.png b/docs/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3e057d5e52c3de7816db69ae6c067e3f40711fda GIT binary patch literal 49046 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 z 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≫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 IM6N<$f(u9e&Hw-a literal 0 HcmV?d00001 diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 00000000..1b4d08c9 --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 6247f7e2..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/markdown-notebooks.md b/docs/markdown-notebooks.md new file mode 100644 index 00000000..a057a320 --- /dev/null +++ b/docs/markdown-notebooks.md @@ -0,0 +1,53 @@ +--- +jupytext: + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.11.5 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Notebooks with MyST Markdown + +Jupyter Book also lets you write text-based notebooks using MyST Markdown. +See [the Notebooks with MyST Markdown documentation](https://jupyterbook.org/file-types/myst-notebooks.html) for more detailed instructions. +This page shows off a notebook written in MyST Markdown. + +## An example cell + +With MyST Markdown, you can define code cells with a directive like so: + +```{code-cell} +print(2 + 2) +``` + +When your book is built, the contents of any `{code-cell}` blocks will be +executed with your default Jupyter kernel, and their outputs will be displayed +in-line with the rest of your content. + +```{seealso} +Jupyter Book uses [Jupytext](https://jupytext.readthedocs.io/en/latest/) to convert text-based files to notebooks, and can support [many other text-based notebook files](https://jupyterbook.org/file-types/jupytext.html). +``` + +## Create a notebook with MyST Markdown + +MyST Markdown notebooks are defined by two things: + +1. YAML metadata that is needed to understand if / how it should convert text files to notebooks (including information about the kernel needed). + See the YAML at the top of this page for example. +2. The presence of `{code-cell}` directives, which will be executed with your book. + +That's all that is needed to get started! + +## Quickly add YAML metadata for MyST Notebooks + +If you have a markdown file and you'd like to quickly add YAML metadata to it, so that Jupyter Book will treat it as a MyST Markdown Notebook, run the following command: + +``` +jupyter-book myst init path/to/markdownfile.md +``` diff --git a/docs/markdown.md b/docs/markdown.md new file mode 100644 index 00000000..faeea606 --- /dev/null +++ b/docs/markdown.md @@ -0,0 +1,55 @@ +# Markdown Files + +Whether you write your book's content in Jupyter Notebooks (`.ipynb`) or +in regular markdown files (`.md`), you'll write in the same flavor of markdown +called **MyST Markdown**. +This is a simple file to help you get started and show off some syntax. + +## What is MyST? + +MyST stands for "Markedly Structured Text". It +is a slight variation on a flavor of markdown called "CommonMark" markdown, +with small syntax extensions to allow you to write **roles** and **directives** +in the Sphinx ecosystem. + +For more about MyST, see [the MyST Markdown Overview](https://jupyterbook.org/content/myst.html). + +## Sample Roles and Directives + +Roles and directives are two of the most powerful tools in Jupyter Book. They +are like functions, but written in a markup language. They both +serve a similar purpose, but **roles are written in one line**, whereas +**directives span many lines**. They both accept different kinds of inputs, +and what they do with those inputs depends on the specific role or directive +that is being called. + +Here is a "note" directive: + +```{note} +Here is a note +``` + +It will be rendered in a special box when you build your book. + +Here is an inline directive to refer to a document: {doc}`markdown-notebooks`. + + +## Citations + +You can also cite references that are stored in a `bibtex` file. For example, +the following syntax: `` {cite}`holdgraf_evidence_2014` `` will render like +this: {cite}`holdgraf_evidence_2014`. + +Moreover, you can insert a bibliography into your page with this syntax: +The `{bibliography}` directive must be used for all the `{cite}` roles to +render properly. +For example, if the references for your book are stored in `references.bib`, +then the bibliography is inserted with: + +```{bibliography} +``` + +## Learn more + +This is just a simple starter to get you started. +You can learn a lot more at [jupyterbook.org](https://jupyterbook.org). diff --git a/docs/notebooks.ipynb b/docs/notebooks.ipynb new file mode 100644 index 00000000..fdb7176c --- /dev/null +++ b/docs/notebooks.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Content with notebooks\n", + "\n", + "You can also create content with Jupyter Notebooks. This means that you can include\n", + "code blocks and their outputs in your book.\n", + "\n", + "## Markdown + notebooks\n", + "\n", + "As it is markdown, you can embed images, HTML, etc into your posts!\n", + "\n", + "![](https://myst-parser.readthedocs.io/en/latest/_static/logo-wide.svg)\n", + "\n", + "You can also $add_{math}$ and\n", + "\n", + "$$\n", + "math^{blocks}\n", + "$$\n", + "\n", + "or\n", + "\n", + "$$\n", + "\\begin{aligned}\n", + "\\mbox{mean} la_{tex} \\\\ \\\\\n", + "math blocks\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "But make sure you \\$Escape \\$your \\$dollar signs \\$you want to keep!\n", + "\n", + "## MyST markdown\n", + "\n", + "MyST markdown works in Jupyter Notebooks as well. For more information about MyST markdown, check\n", + "out [the MyST guide in Jupyter Book](https://jupyterbook.org/content/myst.html),\n", + "or see [the MyST markdown documentation](https://myst-parser.readthedocs.io/en/latest/).\n", + "\n", + "## Code blocks and outputs\n", + "\n", + "Jupyter Book will also embed your code blocks and output in your book.\n", + "For example, here's some sample Matplotlib code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import rcParams, cycler\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "plt.ion()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fixing random state for reproducibility\n", + "np.random.seed(19680801)\n", + "\n", + "N = 10\n", + "data = [np.logspace(0, 1, 100) + np.random.randn(100) + ii for ii in range(N)]\n", + "data = np.array(data).T\n", + "cmap = plt.cm.coolwarm\n", + "rcParams['axes.prop_cycle'] = cycler(color=cmap(np.linspace(0, 1, N)))\n", + "\n", + "\n", + "from matplotlib.lines import Line2D\n", + "custom_lines = [Line2D([0], [0], color=cmap(0.), lw=4),\n", + " Line2D([0], [0], color=cmap(.5), lw=4),\n", + " Line2D([0], [0], color=cmap(1.), lw=4)]\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 5))\n", + "lines = ax.plot(data)\n", + "ax.legend(custom_lines, ['Cold', 'Medium', 'Hot']);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a lot more that you can do with outputs (such as including interactive outputs)\n", + "with your book. For more information about this, see [the Jupyter Book documentation](https://jupyterbook.org)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.0" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/references.bib b/docs/references.bib new file mode 100644 index 00000000..957d6867 --- /dev/null +++ b/docs/references.bib @@ -0,0 +1,57 @@ +--- +--- + +@inproceedings{holdgraf_evidence_2014, + address = {Brisbane, Australia, Australia}, + title = {Evidence for {Predictive} {Coding} in {Human} {Auditory} {Cortex}}, + booktitle = {International {Conference} on {Cognitive} {Neuroscience}}, + publisher = {Frontiers in Neuroscience}, + author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Knight, Robert T.}, + year = {2014} +} + +@article{holdgraf_rapid_2016, + title = {Rapid tuning shifts in human auditory cortex enhance speech intelligibility}, + volume = {7}, + issn = {2041-1723}, + url = {http://www.nature.com/doifinder/10.1038/ncomms13654}, + doi = {10.1038/ncomms13654}, + number = {May}, + journal = {Nature Communications}, + author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Rieger, Jochem W. and Crone, Nathan and Lin, Jack J. and Knight, Robert T. and Theunissen, Frédéric E.}, + year = {2016}, + pages = {13654}, + file = {Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:C\:\\Users\\chold\\Zotero\\storage\\MDQP3JWE\\Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:application/pdf} +} + +@inproceedings{holdgraf_portable_2017, + title = {Portable learning environments for hands-on computational instruction using container-and cloud-based technology to teach data science}, + volume = {Part F1287}, + isbn = {978-1-4503-5272-7}, + doi = {10.1145/3093338.3093370}, + abstract = {© 2017 ACM. There is an increasing interest in learning outside of the traditional classroom setting. This is especially true for topics covering computational tools and data science, as both are challenging to incorporate in the standard curriculum. These atypical learning environments offer new opportunities for teaching, particularly when it comes to combining conceptual knowledge with hands-on experience/expertise with methods and skills. Advances in cloud computing and containerized environments provide an attractive opportunity to improve the effciency and ease with which students can learn. This manuscript details recent advances towards using commonly-Available cloud computing services and advanced cyberinfrastructure support for improving the learning experience in bootcamp-style events. We cover the benets (and challenges) of using a server hosted remotely instead of relying on student laptops, discuss the technology that was used in order to make this possible, and give suggestions for how others could implement and improve upon this model for pedagogy and reproducibility.}, + booktitle = {{ACM} {International} {Conference} {Proceeding} {Series}}, + author = {Holdgraf, Christopher Ramsay and Culich, A. and Rokem, A. and Deniz, F. and Alegro, M. and Ushizima, D.}, + year = {2017}, + keywords = {Teaching, Bootcamps, Cloud computing, Data science, Docker, Pedagogy} +} + +@article{holdgraf_encoding_2017, + title = {Encoding and decoding models in cognitive electrophysiology}, + volume = {11}, + issn = {16625137}, + doi = {10.3389/fnsys.2017.00061}, + abstract = {© 2017 Holdgraf, Rieger, Micheli, Martin, Knight and Theunissen. Cognitive neuroscience has seen rapid growth in the size and complexity of data recorded from the human brain as well as in the computational tools available to analyze this data. This data explosion has resulted in an increased use of multivariate, model-based methods for asking neuroscience questions, allowing scientists to investigate multiple hypotheses with a single dataset, to use complex, time-varying stimuli, and to study the human brain under more naturalistic conditions. These tools come in the form of “Encoding” models, in which stimulus features are used to model brain activity, and “Decoding” models, in which neural features are used to generated a stimulus output. Here we review the current state of encoding and decoding models in cognitive electrophysiology and provide a practical guide toward conducting experiments and analyses in this emerging field. Our examples focus on using linear models in the study of human language and audition. We show how to calculate auditory receptive fields from natural sounds as well as how to decode neural recordings to predict speech. The paper aims to be a useful tutorial to these approaches, and a practical introduction to using machine learning and applied statistics to build models of neural activity. The data analytic approaches we discuss may also be applied to other sensory modalities, motor systems, and cognitive systems, and we cover some examples in these areas. In addition, a collection of Jupyter notebooks is publicly available as a complement to the material covered in this paper, providing code examples and tutorials for predictive modeling in python. The aimis to provide a practical understanding of predictivemodeling of human brain data and to propose best-practices in conducting these analyses.}, + journal = {Frontiers in Systems Neuroscience}, + author = {Holdgraf, Christopher Ramsay and Rieger, J.W. and Micheli, C. and Martin, S. and Knight, R.T. and Theunissen, F.E.}, + year = {2017}, + keywords = {Decoding models, Encoding models, Electrocorticography (ECoG), Electrophysiology/evoked potentials, Machine learning applied to neuroscience, Natural stimuli, Predictive modeling, Tutorials} +} + +@book{ruby, + title = {The Ruby Programming Language}, + author = {Flanagan, David and Matsumoto, Yukihiro}, + year = {2008}, + publisher = {O'Reilly Media} +} + diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt deleted file mode 100644 index 85a301f3..00000000 --- a/docs/requirements-docs.txt +++ /dev/null @@ -1,10 +0,0 @@ -sphinx==4.5.0 -linkify-it-py==2.0.0 -myst-parser==0.18.0 -myst-nb==0.16.0 -ruyaml==0.19.2 -sphinx-autoapi==1.9.0 -pydata-sphinx-theme==0.8.1 -sphinxcontrib-mermaid==0.7.1 -sphinx_book_theme==0.3.3 -jupyter-cache==0.5.0 \ No newline at end of file diff --git a/docs/source/_autoapi_templates/index.rst b/docs/source/_autoapi_templates/index.rst deleted file mode 100644 index 6e01e7ce..00000000 --- a/docs/source/_autoapi_templates/index.rst +++ /dev/null @@ -1,24 +0,0 @@ -:noindex: -:orphan: - -API reference -============= - -This page holds mloq API documentation, which might be helpful for final -users or developers to create their own mloq-based utilities. Among the -different sub-packages and modules, we might differentiate two big categories: -core utilities and high-level ones. - - -* **Core API:** This routines are located within the `mloq.api` that show the different features of the library can be accessed programmatically. - - - -.. toctree:: - :maxdepth: 5 - - {% for page in pages %} - {% if page.top_level_object and page.display %} - {{ page.include_path }} - {% endif %} - {% endfor %} \ No newline at end of file diff --git a/docs/source/_static/mloq.yml b/docs/source/_static/mloq.yml deleted file mode 100644 index a984b995..00000000 --- a/docs/source/_static/mloq.yml +++ /dev/null @@ -1,13 +0,0 @@ -project_name: "`{{project_name}}`" -default_branch: "`{{default_branch}}`" -owner: "`'{{owner}}`" -author: "`{{author}}`" -email: "`{{email}}`" -copyright_holder: "`{{copyright_holder}}`" -project_url: "`{{project_url}}`" -bot_name: "`{{bot_name}}`" -bot_email: "`{{bot_email}}`" -license: "`{{license}}`" -description: "`{{description}}`" -python_versions: "`{{python_versions}}`" -docker_image: "`{{docker_image}}`" \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 66adcab4..00000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,152 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -from pathlib import Path -import sys - -from ruamel.yaml import load as yaml_load, Loader - - -sys.path.insert(0, os.path.abspath("../../")) -sys.setrecursionlimit(1500) - - -def read_template() -> dict: - """Load the project configuration from the target path.""" - template_path = Path(__file__).parent / "_static" / "mloq.yml" - with open(template_path, "r") as config: - params = yaml_load(config.read(), Loader) - return params - - -# -- Project information ----------------------------------------------------- -project = "MLOQ" -copyright = "2020-2022, FragileTech" -author = "Guillem Duran, Vadim Markovtsev" - -# The short X.Y version -from mloq.version import __version__ - - -version = __version__ -# The full version, including alpha/beta/rc tags -release = __version__ -# -- General configuration --------------------------------------------------- -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ["_build", "**.ipynb_checkpoints"] -# The master toctree document. -master_doc = "index" -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - # "sphinx.ext.autodoc", - "autoapi.extension", - "sphinx.ext.doctest", - "sphinx.ext.intersphinx", - "sphinx.ext.todo", - "sphinx.ext.coverage", - "sphinx.ext.imgmath", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx.ext.autosectionlabel", - "sphinx.ext.autodoc.typehints", - "sphinx_book_theme", - "myst_nb", - "sphinxcontrib.mermaid", - "sphinx.ext.githubpages", -] -suppress_warnings = ["image.nonlocal_uri"] -autodoc_typehints = "description" -# Autoapi settings -autoapi_type = "python" -autoapi_dirs = ["../../src/mloq"] -autoapi_add_toctree_entry = True -# Make use of custom templates -autoapi_template_dir = "_autoapi_templates" -exclude_patterns.append("_autoapi_templates/index.rst") - -# Ignore sphinx-autoapi warnings on multiple target description -suppress_warnings.append("ref.python") - -# Napoleon settings -napoleon_google_docstring = True -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = True -napoleon_include_private_with_doc = False -napoleon_include_special_with_doc = True -napoleon_use_admonition_for_examples = False -napoleon_use_admonition_for_notes = False -napoleon_use_admonition_for_references = False -napoleon_use_ivar = False -napoleon_use_param = True -napoleon_use_rtype = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_title = "" -html_theme = "sphinx_book_theme" -# html_logo = "_static/logo-wide.svg" -# html_favicon = "_static/logo-square.svg" -html_theme_options = { - "github_url": "https://github.com/FragileTech/ml-ops-quickstart", - "repository_url": "https://github.com/FragileTech/ml-ops-quickstart", - "repository_branch": "gh-pages", - "home_page_in_toc": True, - "path_to_docs": "docs", - "show_navbar_depth": 1, - "use_edit_page_button": True, - "use_repository_button": True, - "use_download_button": True, - "launch_buttons": { - "binderhub_url": "https://mybinder.org", - "notebook_interface": "classic", - }, -} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# myst_parser options -myst_heading_anchors = 2 -myst_enable_extensions = [ - "amsmath", - "colon_fence", - "deflist", - "dollarmath", - "html_admonition", - "html_image", - "linkify", - "replacements", - "smartquotes", - "substitution", -] -# myst_substitutions = read_template() - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True diff --git a/docs/source/index.md b/docs/source/index.md deleted file mode 100644 index 79003cbe..00000000 --- a/docs/source/index.md +++ /dev/null @@ -1,53 +0,0 @@ -# Welcome to ML Ops Quickstart -[![Code coverage](https://codecov.io/github/fragiletech/ml-ops-quickstart/coverage.svg)](https://codecov.io/github/fragiletech/ml-ops-quickstart) -[![PyPI package](https://badgen.net/pypi/v/mloq)](https://pypi.org/project/mloq/) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) -[![license: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) - -ML Ops Quickstart is a tool for initializing Machine Learning projects following ML Ops best practices. - -Setting up new repositories is a time-consuming task that involves creating different files and -configuring tools such as linters, docker containers and continuous integration pipelines. -The goal of `mloq` is to simplify that process, so you can start writing code as fast as possible. - -`mloq` generates customized templates for Python projects with focus on Maching Learning. An example of -the generated templates can be found in [mloq-template](https://github.com/FragileTech/mloq-template). - -```{toctree} ---- -maxdepth: 5 -caption: Welcome to MLOQ ---- -markdown/welcome.md -``` -```{toctree} ---- -maxdepth: 5 -caption: How to ---- -markdown/usage.md -``` -```{toctree} ---- -maxdepth: 5 -caption: Features ---- -markdown/features.md -``` -```{toctree} ---- -maxdepth: 5 -caption: MLOQ Library ---- -markdown/library.md -autoapi/index.rst -``` - - - - -# Indices and tables - -{ref}`genindex` -{ref}`modindex` -{ref}`search` diff --git a/docs/source/markdown/features.md b/docs/source/markdown/features.md deleted file mode 100644 index 1dc0ef20..00000000 --- a/docs/source/markdown/features.md +++ /dev/null @@ -1,82 +0,0 @@ -`mloq` offers a set of features focused on generating automatically all the required -files to start your Machine Learning project. - -## Repository files -Set up the following common repository files personalized for your project with the values -defined in `mloq.yml`: - -- [README.md](../assets/templates/README.md) -- [DCO.md](../assets/static/DCO.md) -- [CONTRIBUTING.md](../assets/templates/CONTRIBUTING.md) -- [CODE_OF_CONDUCT.md](../assets/templates/CODE_OF_CONDUCT.md) -- [LICENSE](../assets/templates/MIT_LICENSE) -- [.gitignore](../assets/static/.gitignore) - -## Packaging - -Automatic configuration of `pyproject.toml` and `setup.py` to distribute your project as a Python package. - -## Code style -All the necessary configuration for the following tools is defined in [pyproject.toml](../assets/templates/pyproject.toml). -- [black](https://black.readthedocs.io/en/stable/?badge=stable): Automatic code formatter. -- [isort](https://pycqa.github.io/isort/): Rearrange your imports automatically. -- [flakehell](https://flakehell.readthedocs.io/): Linter tool build on top of `flake8`, `pylint` and `pycodestyle` - -## Requirements -`mloq` creates three different requirements files in the root directory of the project. Each file contains pinned dependencies. - -- [requirements-lint.txt](../assets/requirements/requirements-lint.txt): -Contains the dependencies for running style check analysis and automatic formatting of the code. -- [requirements-test.txt](../assets/requirements/requirements-test.txt): - -Dependencies for running pytest, hypothesis, and test coverage. - -- `requirements.txt`: Contains different pre-configured dependencies that can be defined in `mloq.yml`. The available pre-configured dependencies are: - * [data-science](../assets/requirements/data-science.txt): Dependencies of common data science libraries. - * [data-visualization](../assets/requirements/data-visualization.txt): Common visualization libraries. - * Last version of [pytorch](../assets/requirements/pytorch.txt) and [tensorflow](../assets/requirements/tensorflow.txt) - -## Docker - -A [Dockerfile](../assets/templates/Dockerfile) that builds a container on top of the FragileTech [Docker Hub](https://hub.docker.com/orgs/fragiletech/repositories) images: -- If *tensorflow* or *pytorch* are selected as requirements, the container has CUDA 11.0 installed. -- Installs all the packages listed in `requirements.txt`. -- Installs `requirements-test.txt` and `requirements-lint.txt` dependencies. -- Install a `jupyter notebook` server with a configurable password on port 8080. -- Installs the project with `pip install -e .`. - -## Continuous integration using GitHub Actions -Set up automatically a continuous integration (CI) pipeline using GitHub actions with the following jobs: -![GitHub Actions pipeline](../../images/ci_python.png) - -Automatic build and tests: - -- **Style Check**: Run `flake8` and `black --check` to ensure a consistent code style. -- **Pytest**: Test the project using pytest on all supported Python versions and output a code coverage report. -- **Test-docker**: Build the project's Docker container and run the tests inside it. -- **Build-pypi**: Build the project and upload it to [Test Pypi](https://test.pypi.org/) with a version tag unique to each commit. -- **Test-pypi**: Install the project from Test Pypi and run the tests using pytest. -- **Bump-version**: Automatically bump the project version and create a tag in the repository every time the default branch is updated. - -Deploy each new version: -- **Push-docker-container**: Upload the project's Docker container to [Docker Hub](https://hub.docker.com/). -- **Release-package**: Upload to [Pypi](https://pypi.org/) the source of the project and the corresponding wheels. - -## Testing -The last versions of `pytest`, `hypothesis`, and `pytest-cov` can be found in `requirements-test.txt`. - -The folder structure for the library and tests is created. - -## Project Makefile -A `Makefile` will be created in the root directory of the project. It contains the following commands: - -- `make style`: Run `isort` and `black` to automatically arrange the imports and format the project. -- `make check`: Run `flakehell` and check black style. If it raises any error the CI will fail. -- `make test`: Clear the tests cache and run pytest. -- `make pipenv-install`: Install the project in a new Pipenv environment and create a new `Pipfile` and `Pipfile.lock`. -- `make pipenv-test`: Run pytest inside the project's Pipenv. -- `make docker-build`: Build the project's Docker container. -- `make docker-test`: Run pytest inside the projects docker container. -- `make docker-shell`: Mount the current project as a docker volume and open a terminal in the project's container. -- `make docker-notebook`: Mount the current project as a docker volume and open a jupyter notebook in the project's container. - It exposes the notebook server on the port `8080`. diff --git a/docs/source/markdown/library.md b/docs/source/markdown/library.md deleted file mode 100644 index 8e8b8e41..00000000 --- a/docs/source/markdown/library.md +++ /dev/null @@ -1,19 +0,0 @@ -## License -ML Ops Quickstart is released under the [MIT](https://opensource.org/licenses/MIT) license. - -## Contributing - -Contributions are very welcome! Please check the contributing guidelines before opening a pull request. - -```{include} ../../../CONTRIBUTING.md -:relative-docs: docs/ -:relative-images: -``` - -## Roadmap - -- [ ] Improve documentation and test coverage. -- [ ] Configure `sphinx` to build the docs automatically. -- [ ] Implement checks for additional best practices. -- [ ] Improve command\-line interface and logging. -- [ ] Add new customization options. \ No newline at end of file diff --git a/docs/source/markdown/tutorial.md b/docs/source/markdown/tutorial.md deleted file mode 100644 index c9a7a3b9..00000000 --- a/docs/source/markdown/tutorial.md +++ /dev/null @@ -1,11 +0,0 @@ -# Command - -## What is its goal? - -## What tools does it configure? - -## What files does it write? in what directories? - -## What are its parameters? - -## Futher customization \ No newline at end of file diff --git a/docs/source/markdown/usage.md b/docs/source/markdown/usage.md deleted file mode 100644 index 28085838..00000000 --- a/docs/source/markdown/usage.md +++ /dev/null @@ -1,64 +0,0 @@ -## Command line interface - -Options: -* `--file` `-f`: Name of the configuration file. If `file` is a directory, it will load the `mloq.yml` file present in it. - -* `--overwrite` `-o`: Rewrite files that already exist in the target project. -* `--interactive` `-i`: Missing configuration data can be defined interactively from the CLI. - -## Usage examples -Arguments: -* `OUTPUT_DIRECTORY`: Path to the target project. - -To set up a new repository from scratch interactively in the current working directory: -```bash -mloq setup -i . -``` - -To load a `mloq.yml` configuration file from the current repository, and initialize the directory `example`, and -overwrite all existing files with no interactivity: -```bash -mloq setup -f . -o example -``` - -![ci python](../../images/mloq_setup.png) - - -## mloq.yml config file - -This yaml file contains all the information used by mloq to set up a new project. -All values are strings except **python_versions** and **requirements**, -which are lists of strings. **null** values are interpreted as missing values. -```yaml -# This yaml file contains all the information used by mloq to set up a new project. -# All values in template are strings and booleans, -# except "python_versions" and "requirements" that are lists of strings. -# "null" values are interpreted as non-defined values. -# ------------------------------------------------------------------------------ - -# project_config values are necessary to define the files that will be written, and the tools -# that will be configured. -project_config: - open_source: null # boolean. If True, set up and Open Source project - docker: null # boolean If True, set up a Docker image for the project - ci: null # Name of the GitHub Actions CI workflow that will be configured. - mlflow: null # boolean. If True configure a MLproject file compatible with ML Flow projects. - requirements: null # List containing the pre-defined requirements of the project. - -# template contains all the values that will be written in the generated files. -# They are loaded as a dictionary and passed to jinja2 to fill in the templates. -template: - project_name: null # Name of the new Python project - default_branch: null # Name of the default branch. Used in the CI push workflow. - owner: null # Github handle of the project owner - author: null # Person(s) or entity listed as the project author in setup.py - email: null # Owner contact email - copyright_holder: null # Owner of the project copyright. - project_url: null # Project download url. Defaults to https://github.com/{owner}/{project_name} - bot_name: null # GitHub login of the account used to push when bumping the project version - bot_email: null # Bot account email - license: null # Currently only proprietary and MIT license is supported - description: null # Short description of the project - python_versions: null # Supported Python versions - docker_image: null # Your project Docker container will inherit from this image. -``` \ No newline at end of file diff --git a/docs/source/markdown/welcome.md b/docs/source/markdown/welcome.md deleted file mode 100644 index a02a17f3..00000000 --- a/docs/source/markdown/welcome.md +++ /dev/null @@ -1,29 +0,0 @@ -## About ML Ops Quickstart -[![Code coverage](https://codecov.io/github/fragiletech/ml-ops-quickstart/coverage.svg)](https://codecov.io/github/fragiletech/ml-ops-quickstart) -[![PyPI package](https://badgen.net/pypi/v/mloq)](https://pypi.org/project/mloq/) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) -[![license: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) - -ML Ops Quickstart is a tool for initializing Machine Learning projects following ML Ops best practices. - -Setting up new repositories is a time-consuming task that involves creating different files and -configuring tools such as linters, docker containers, and continuous integration pipelines. -The goal of `mloq` is to simplify that process, so you can start writing code as fast as possible. - -`mloq` generates customized templates for Python projects with a focus on Maching Learning. An example of -the generated templates can be found in [mloq-template](https://github.com/FragileTech/mloq-template). - -## Installation - -`mloq` is tested on Ubuntu 18.04+, and supports Python 3.6+. - -### Install from pypi -```bash -pip install mloq -``` -### Install from source -```bash -git clone https://github.com/FragileTech/ml-ops-quickstart.git -cd ml-ops-quickstart -pip install -e . -``` \ No newline at end of file diff --git a/mloq.yaml b/mloq.yaml deleted file mode 100644 index 50afaa8f..00000000 --- a/mloq.yaml +++ /dev/null @@ -1,147 +0,0 @@ -globals: - project_name: mloq - default_branch: master - owner: FragileTech - author: FragileTech - email: guillem@fragile.tech - description: Package for initializing ML projects following ML Ops best practices. - open_source: true - project_url: https://github.com/FragileTech/ml-ops-quickstart - license: "MIT" - use_poetry: false - main_python_version: "3.8" - -license: - disable: false - license: MIT - copyright_year: 2020 - copyright_holder: ${globals.owner} - project_name: ${globals.project_name} - email: ${globals.email} - project_url: ${globals.project_url} - -project: - disable: false - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - project_url: ${globals.project_url} - tests: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - deploy_docs: true - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${globals.license} - use_poetry: ${globals.use_poetry} - main_python_version: ${globals.main_python_version} - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - license_classifier: "License :: OSI Approved :: MIT License" - -requirements: - disable: false - requirements: - - dogfood - -lint: - disable: false - black: True - isort: True - linters: True - poetry_requirements: True - docstring_checks: true - pyproject_extra: |- - [tool.flakehell.exceptions."**/assets/*"] - pycodestyle = ["-*"] - pyflakes = ["-*"] - "flake8*" = ["-*"] - project_name: ${globals.project_name} - makefile: true - -docker: - disable: false - cuda: "???" - cuda_image_type: "cudnn8-runtime" - cuda_version: "11.2" - python_version: "3.8" - ubuntu_version: "20.04" - base_image: "???" - test: true - lint: true - jupyter: true - jupyter_password: ${globals.project_name} - project_name: ${globals.project_name} - docker_org: fragiletech #${globals.owner} - requirements: ${requirements.requirements} - makefile: true - extra: "" - -ci: - bot_name: fragile-bot - bot_email: bot@fragile.tech - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: fragiletech - docker: true - python_versions: ${package.python_versions} - ci_extra: |- - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - #diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py \ No newline at end of file diff --git a/notebooks/__init__.py b/notebooks/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pyproject.toml b/pyproject.toml index 3f0beec0..544b768b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,52 +1,66 @@ +[build-system] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" + [project] -name = "mloq" -dynamic = ["version"] -description = "Package for initializing ML projects following ML Ops best practices." +name = "ml-ops-quickstart" +description = "Automate project creation following ML best practices." readme = "README.md" -license = {text = "MIT"} +requires-python = ">=3.10" +license = { file = "LICENSE" } +# keywords for easier look-up on PyPI +keywords = [] # ToDo: Modify according to your needs! authors = [ - {name = "FragileTech", email = "guillem@fragile.tech"} + { name = "Guillem Duran Ballester", email = "guillem@fragile.tech" }, ] -requires-python = ">=3.9" -homepage = "https://github.com/FragileTech/ml-ops-quickstart" -repository = "https://github.com/FragileTech/ml-ops-quickstart" -keywords = ["Machine learning", "artificial intelligence"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3 :: Only", - "Topic :: Software Development :: Libraries" +maintainers = [ + { name = "Guillem Duran Ballester", email = "guillem@fragile.tech" }, ] +# options under https://pypi.org/classifiers/ +classifiers = [ + # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: Unix", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + # uncomment if you test on these interpreters: + # "Programming Language :: Python :: Implementation :: IronPython", + # "Programming Language :: Python :: Implementation :: Jython", + # "Programming Language :: Python :: Implementation :: Stackless", + "Topic :: Utilities", + ] +dynamic = ["version"] +# direct dependencies of this package, installed when users `pip install ml-ops-quickstart` later. dependencies = [ "flogging", - "jinja2", - "click", - "invoke", - "hydra-core", - "param", "pre-commit", - "mypy>=1.11.2", -] - + "click", +] # ToDo: Modify according to your needs! +[project.urls] +# important URLs for this project +Documentation = "https://ml-ops-quickstart.readthedocs.io/" +Changelog = "https://ml-ops-quickstart.readthedocs.io/en/latest/changelog.html" +Tracker = "https://github.com/FragileTech/ml-ops-quickstart/issues" [project.scripts] -mloq = "mloq.cli:cli" +mloq = "mloq.__main__:run" -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" -[tool.hatch.metadata] -allow-direct-references = true -[tool.hatch.version] -path = "src/mloq/version.py" - -[tool.rye] -dev-dependencies = [ - "ruff", +[project.optional-dependencies] +lint= ["mypy", "ruff"] +test = ["pytest", "pytest-cov", "pytest-xdist"] +docs = [ + "autoapi", + "jupyter-book", "sphinx", "linkify-it-py", "myst-parser", @@ -63,35 +77,46 @@ dev-dependencies = [ "sphinx-togglebutton", "sphinxext-opengraph", "sphinxcontrib-bibtex", - "psutil>=5.8.0", - "pytest>=6.2.5", - "pytest-cov>=3.0.0", - "pytest-xdist>=2.4.0", - "pytest-rerunfailures>=10.2", - "pyvirtualdisplay>=1.3.2", - "tomli>=1.2.3", - "hypothesis>=6.24.6" - ] +[tool.rye] +managed = true universal = true +dev-dependencies = ["hatch"] +[tool.hatch.metadata] +# direct dependency references, e.g `pip @ git+https://github.com/pypa/pip.git@master` +allow-direct-references = true + +[tool.hatch.version] +path = "src/mloq/version.py" + +[tool.hatch.build] +packages = ["src/mloq"] + +[tool.hatch.build.targets.sdist] +exclude = [ + "/.github", +] [tool.rye.scripts] -style = { chain = ["ruff check --fix-only --unsafe-fixes tests src", "ruff format tests src"] } -check = { chain = ["ruff check --diff tests src", "ruff format --diff tests src"]} #,"mypy src tests" ] } -test = { chain = ["test:doctest", "test:parallel"] } -codecov = { cmd = "pytest -n auto -s -o log_cli=true -o log_cli_level=info --cov=./ --cov-report=xml --cov-config=pyproject.toml tests" } -"test:parallel" = { cmd = "pytest -n auto -s -o log_cli=true -o log_cli_level=info tests" } -"test:singlecore" = { cmd = "pytest -s -o log_cli=true -o log_cli_level=info tests" } -"test:doctest" = { cmd = "pytest --doctest-modules -n 0 -s -o log_cli=true -o log_cli_level=info src" } -docs = {chain = ["build-docs", "serve-docs"]} -build-docs = { cmd = "sphinx-build -b html docs/source docs/build"} -serve-docs = { cmd = "python3 -m http.server --directory docs/build" } +lint = { cmd = "hatch run lint:all" } +style = { cmd = "hatch run lint:style" } +check = { cmd = "hatch run lint:check" } +test = { cmd = "hatch run test:test" } +doctest = { cmd = "hatch run test:doctest" } +cov = { cmd = "hatch run test:cov" } +no-cov = { cmd = "hatch run test:no-cov" } +debug = { cmd = "hatch run test:debug" } +docs = { cmd = "hatch run docs:docs" } +build-docs = { cmd = "hatch run docs:build"} +sphinx = { cmd = "hatch run docs:sphinx" } +serve-docs = { cmd = "hatch run docs:serve" } [tool.ruff] # Assume Python 3.10 target-version = "py310" + preview = true -include = ["*.py", "*.pyi", "**/pyproject.toml"]#, "*.ipynb"] +include = ["*.py", "*.pyi", "**/pyproject.toml"] # Exclude a variety of commonly ignored directories. exclude = [ ".bzr", @@ -140,12 +165,26 @@ ignore = [ "D100", "D211", "D213", "D104", "D203", "D301", "D407", "S101", "FBT001", "FBT002", "FIX002", "ISC001", "PLR0913", "RUF012", "TD003", "PTH123", "PLR6301", "PLR0917", "S311", "S403", "PLR0914", "PLR0915", "S608", - "EM102", "PTH111", "FIX004", "UP035", "PLW2901", "S318", "S408", 'S405', - 'E902', "TD001", "TD002", "FIX001", + "EM102", "PTH111", "FIX004", "UP035", "PLW2901", "S318", "S404", + "S408", 'S405', 'S607', 'S603', + 'E902', "TD001", "TD002", "FIX001", "T201", ] # Allow autofix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] -unfixable = ["I"] +# unfixable = ["I"] + +[tool.ruff.lint.flake8-import-conventions.aliases] +# Declare the default aliases. +altair = "alt" +"matplotlib.pyplot" = "plt" +numpy = "np" +pandas = "pd" +seaborn = "sns" +scipy = "sp" +holoviews = "hv" +panel = "pn" +polars = "pl" +"polars.selectors" = "cs" [tool.ruff.lint.flake8-quotes] docstring-quotes = "double" @@ -153,46 +192,185 @@ docstring-quotes = "double" [tool.ruff.lint.per-file-ignores] "__init__.py" = ["E402", "F401"] "cli.py" = ["PLC0415", "D205", "D400", "D415"] -"core.py" = ["ARG002", "PLR0904"] -"_old_core.py" = ["ALL"] -"lunar_lander.py" = ["PLR2004", "FBT003", "N806"] -"api_tests.py" = ["D", "ARG002", "PLW1508", "FBT003", "PLR2004"] -"montezuma.py" = ["PLR2004", "S101", "ARG002", "TD002"] -"registry.py" = ["PLC0415", "PLR0911"] "**/docs/**" = ["INP001", "PTH100"] -"**/super_mario_gym/**" = ["ALL"] -"**/{tests,docs,tools}/*" = [ +"**/{tests,docs}/*" = [ "E402", "F401", "F811", "D", "S101", "PLR2004", "S105", "PLW1514", "PTH123", "PTH107", "N811", "PLC0415", "ARG002", ] # Enable reformatting of code snippets in docstrings. [tool.ruff.format] -docstring-code-line-length = 80 +docstring-code-line-length = 99 docstring-code-format = true indent-style = "space" line-ending = "auto" preview = true quote-style = "double" +[tool.isort] +profile = "black" +line_length = "99" +multi_line_output = 3 +order_by_type = false +force_alphabetical_sort_within_sections = true +force_sort_within_sections = true +combine_as_imports = true +include_trailing_comma = true +color_output = true +lines_after_imports = 2 +honor_noqa = true +skip = [".venv", "venv"] +skip_glob = ["*.pyx"] + +[tool.ruff.lint.isort] +known-first-party = ["mloq"] +forced-separate = ["conftest"] +force-single-line = true + +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false + +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "all" + [tool.mypy] -exclude = ["experimental.*", "deprecated.*"] +files = ["src/mloq", "tests"] +disallow_untyped_defs = false +follow_imports = "normal" # "silent" for not following ignore_missing_imports = true +pretty = true +show_column_numbers = true +warn_no_return = false +warn_unused_ignores = true + +[tool.pylint.master] +ignore = 'tests' +load-plugins =' pylint.extensions.docparams' + +[tool.pylint.messages_control] +disable = 'all,' +enable = """, + missing-param-doc, + differing-param-doc, + differing-type-doc, + missing-return-doc, + """ + +[tool.pytest.ini_options] +# To disable a specific warning --> action:message:category:module:line +filterwarnings = ["ignore::UserWarning", 'ignore::DeprecationWarning'] +addopts = "--ignore=scripts --doctest-continue-on-failure" +# Code coverage config [tool.coverage.run] branch = true source = ["src/mloq"] +omit = [ + "version.py", # automatically created by hatch-vcs, not in repo +] +[tool.coverage.paths] +source = [ + "src/", + "*/site-packages/", +] [tool.coverage.report] +# Regexes for lines to exclude from consideration exclude_lines = [ - "no cover", + # Have to re-enable the standard pragma + "pragma: no cover", + + # Don't complain about missing debug-only code: + "def __repr__", + "if self\\.debug", + + # Don't complain if tests don't hit defensive assertion code: + "raise AssertionError", "raise NotImplementedError", - "if __name__ == '__main__':" + + # Don't complain if non-runnable code isn't run: + "if 0:", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", ] ignore_errors = true omit = ["tests/*"] -[tool.pytest.ini_options] -testpaths = ["tests"] -# To disable a specific warning --> action:message:category:module:line -filterwarnings = ["ignore::UserWarning", 'ignore::DeprecationWarning'] -addopts = "--ignore=scripts --doctest-continue-on-failure" +##################### +# Environment Setup # +##################### + +# Default environment with production dependencies +[tool.hatch.envs.default] +installer = "uv" +python = "3.10" +post-install-commands = ["pre-commit install"] +dependencies = [] +# Test environment with test-only dependencies +[tool.hatch.envs.test] +description = "Run tests and coverage" +features = ["test"] +[tool.hatch.envs.test.scripts] +run_pytest = "pytest -s -o log_cli=true -o log_cli_level=info {args}" +doctest = "run_pytest --doctest-modules -n 0 {args:src}" +test = ["doctest", "run_pytest {args:tests}"] +cov = "run_pytest -n auto --cov-report=term-missing --cov-config=pyproject.toml --cov=src/mloq --cov=tests {args}" +no-cov = "cov --no-cov {args}" +debug = "cov --no-cov --pdb --pdbcls=IPython.core.debugger:Pdb {args}" +[tool.hatch.envs.test.env-vars] +N = "auto" +# Docs environment +[tool.hatch.envs.docs] +description = "Build and serve documentation using Jupyter Book and Sphinx" +features = ["docs"] +[tool.hatch.envs.docs.env-vars] +SOURCE_DATE_EPOCH = "1580601600" +PYTHONUNBUFFERED = "1" +[tool.hatch.envs.docs.scripts] +features = ["docs"] +build = [ + "jupyter-book build docs/ ", + "uv pip freeze > docs/requirements.txt", +] +validate = "linkchecker --config .linkcheckerrc --ignore-url=/reference --ignore-url=None site" +build-check = ["build"] #, "validate"] +serve = "python3 -m http.server --directory docs/_build/html {args}" +sphinx = [ + "jupyter-book config sphinx docs/ --overwrite", + "sphinx-build -b html docs/ docs/_build/html", + "uv pip freeze > docs/requirements.txt", +] + +docs = ["build", "serve"] +# Lint environment +[tool.hatch.envs.lint] +description = "Run linting checks with ruff and mypi" +template = "lint" # don't inherit from default! +features = ["lint"] +skip-install = true +dependencies = [ + "mypy", + "ruff", +] +[tool.hatch.envs.lint.scripts] +typing = [ + "echo \"MYPY VERSION: `mypy --version`\"", + "mypy --install-types --non-interactive {args}" +] +style = [ + "echo \"RUFF VERSION: `ruff --version`\"", + "ruff check --fix-only --unsafe-fixes {args:.}", "ruff format {args:.}", +] +check = [ + "ruff check {args:.}", "ruff format --diff {args:.}","mypy {args:.}" , +] +all = [ + "style", + "check", + "typing", +] + +# Test matrix for various Python versions replacing the functionality of tox +[[tool.hatch.envs.py-test.matrix]] +template = ["test"] +python = ["39", "310", "311", "312"] diff --git a/requirements-dev.lock b/requirements-dev.lock deleted file mode 100644 index 47eff25f..00000000 --- a/requirements-dev.lock +++ /dev/null @@ -1,331 +0,0 @@ -# generated by rye -# use `rye lock` or `rye sync` to update this lockfile -# -# last locked with the following flags: -# pre: false -# features: [] -# all-features: true -# with-sources: false -# generate-hashes: false -# universal: true - --e file:. -accessible-pygments==0.0.5 - # via pydata-sphinx-theme -alabaster==1.0.0 - # via sphinx -antlr4-python3-runtime==4.9.3 - # via hydra-core - # via omegaconf -appnope==0.1.4 ; platform_system == 'Darwin' - # via ipykernel -astroid==3.3.4 - # via sphinx-autoapi - # via sphinx-autodoc2 -asttokens==2.4.1 - # via stack-data -attrs==24.2.0 - # via hypothesis - # via jsonschema - # via jupyter-cache - # via referencing -babel==2.16.0 - # via pydata-sphinx-theme - # via sphinx -beautifulsoup4==4.12.3 - # via pydata-sphinx-theme -certifi==2024.8.30 - # via requests -cffi==1.17.1 ; implementation_name == 'pypy' - # via pyzmq -cfgv==3.4.0 - # via pre-commit -charset-normalizer==3.3.2 - # via requests -click==8.1.7 - # via jupyter-cache - # via mloq -colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows' - # via click - # via ipython - # via pytest - # via sphinx -comm==0.2.2 - # via ipykernel -coverage==7.6.1 - # via pytest-cov -debugpy==1.8.6 - # via ipykernel -decorator==5.1.1 - # via ipython -distlib==0.3.8 - # via virtualenv -distro==1.9.0 - # via ruyaml -docutils==0.21.2 - # via myst-parser - # via pybtex-docutils - # via pydata-sphinx-theme - # via sphinx - # via sphinx-togglebutton - # via sphinxcontrib-bibtex -exceptiongroup==1.2.2 ; python_full_version < '3.11' - # via hypothesis - # via ipython - # via pytest -execnet==2.1.1 - # via pytest-xdist -executing==2.1.0 - # via stack-data -fastjsonschema==2.20.0 - # via nbformat -filelock==3.16.1 - # via virtualenv -flogging==0.0.23 - # via mloq -greenlet==3.1.1 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64') - # via sqlalchemy -hydra-core==1.3.2 - # via mloq -hypothesis==6.112.1 -identify==2.6.1 - # via pre-commit -idna==3.10 - # via requests -imagesize==1.4.1 - # via sphinx -importlib-metadata==8.5.0 - # via jupyter-cache - # via myst-nb -iniconfig==2.0.0 - # via pytest -invoke==2.2.0 - # via mloq -ipykernel==6.29.5 - # via myst-nb -ipython==8.27.0 - # via ipykernel - # via myst-nb -jedi==0.19.1 - # via ipython -jinja2==3.1.4 - # via mloq - # via myst-parser - # via sphinx - # via sphinx-autoapi -jsonschema==4.23.0 - # via nbformat -jsonschema-specifications==2023.12.1 - # via jsonschema -jupyter-cache==1.0.0 - # via myst-nb -jupyter-client==8.6.3 - # via ipykernel - # via nbclient -jupyter-core==5.7.2 - # via ipykernel - # via jupyter-client - # via nbclient - # via nbformat -latexcodec==3.0.0 - # via pybtex -linkify-it-py==2.0.3 -markdown-it-py==3.0.0 - # via mdit-py-plugins - # via myst-parser -markupsafe==2.1.5 - # via jinja2 -matplotlib-inline==0.1.7 - # via ipykernel - # via ipython -mdit-py-plugins==0.4.2 - # via myst-parser -mdurl==0.1.2 - # via markdown-it-py -mypy==1.11.2 - # via mloq -mypy-extensions==1.0.0 - # via mypy -myst-nb==1.1.2 -myst-parser==4.0.0 - # via myst-nb -nbclient==0.10.0 - # via jupyter-cache - # via myst-nb -nbformat==5.10.4 - # via jupyter-cache - # via myst-nb - # via nbclient -nest-asyncio==1.6.0 - # via ipykernel -nodeenv==1.9.1 - # via pre-commit -omegaconf==2.3.0 - # via hydra-core -packaging==24.1 - # via hydra-core - # via ipykernel - # via pydata-sphinx-theme - # via pytest - # via pytest-rerunfailures - # via sphinx -param==2.1.1 - # via mloq -parso==0.8.4 - # via jedi -pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' - # via ipython -platformdirs==4.3.6 - # via jupyter-core - # via virtualenv -pluggy==1.5.0 - # via pytest -pre-commit==3.8.0 - # via mloq -prompt-toolkit==3.0.48 - # via ipython -psutil==6.0.0 - # via ipykernel -ptyprocess==0.7.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' - # via pexpect -pure-eval==0.2.3 - # via stack-data -pybtex==0.24.0 - # via pybtex-docutils - # via sphinxcontrib-bibtex -pybtex-docutils==1.0.3 - # via sphinxcontrib-bibtex -pycparser==2.22 ; implementation_name == 'pypy' - # via cffi -pydata-sphinx-theme==0.15.4 - # via sphinx-book-theme -pygments==2.18.0 - # via accessible-pygments - # via ipython - # via pydata-sphinx-theme - # via sphinx -pytest==8.3.3 - # via pytest-cov - # via pytest-rerunfailures - # via pytest-xdist -pytest-cov==5.0.0 -pytest-rerunfailures==14.0 -pytest-xdist==3.6.1 -python-dateutil==2.9.0.post0 - # via jupyter-client -pyvirtualdisplay==3.0 -pywin32==306 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' - # via jupyter-core -pyyaml==6.0.2 - # via jupyter-cache - # via myst-nb - # via myst-parser - # via omegaconf - # via pre-commit - # via pybtex - # via sphinx-autoapi -pyzmq==26.2.0 - # via ipykernel - # via jupyter-client -referencing==0.35.1 - # via jsonschema - # via jsonschema-specifications -requests==2.32.3 - # via sphinx -rpds-py==0.20.0 - # via jsonschema - # via referencing -ruff==0.6.7 -ruyaml==0.91.0 -setuptools==75.1.0 - # via ruyaml - # via sphinx-togglebutton - # via sphinxcontrib-bibtex -six==1.16.0 - # via asttokens - # via pybtex - # via python-dateutil -snowballstemmer==2.2.0 - # via sphinx -sortedcontainers==2.4.0 - # via hypothesis -soupsieve==2.6 - # via beautifulsoup4 -sphinx==8.0.2 - # via myst-nb - # via myst-parser - # via pydata-sphinx-theme - # via sphinx-autoapi - # via sphinx-book-theme - # via sphinx-copybutton - # via sphinx-rtd-theme - # via sphinx-togglebutton - # via sphinxcontrib-bibtex - # via sphinxext-opengraph -sphinx-autoapi==3.3.1 -sphinx-autodoc2==0.5.0 -sphinx-book-theme==1.1.3 -sphinx-copybutton==0.5.2 -sphinx-rtd-theme==0.5.1 -sphinx-togglebutton==0.3.2 -sphinxcontrib-applehelp==2.0.0 - # via sphinx -sphinxcontrib-bibtex==2.6.3 -sphinxcontrib-devhelp==2.0.0 - # via sphinx -sphinxcontrib-htmlhelp==2.1.0 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-mermaid==0.9.2 -sphinxcontrib-qthelp==2.0.0 - # via sphinx -sphinxcontrib-serializinghtml==2.0.0 - # via sphinx -sphinxext-opengraph==0.9.1 -sqlalchemy==2.0.35 - # via jupyter-cache -stack-data==0.6.3 - # via ipython -tabulate==0.9.0 - # via jupyter-cache -tomli==2.0.1 - # via coverage - # via mypy - # via pytest - # via sphinx - # via sphinx-autodoc2 -tornado==6.4.1 - # via ipykernel - # via jupyter-client -traitlets==5.14.3 - # via comm - # via ipykernel - # via ipython - # via jupyter-client - # via jupyter-core - # via matplotlib-inline - # via nbclient - # via nbformat -typing-extensions==4.12.2 - # via astroid - # via ipython - # via mypy - # via myst-nb - # via pydata-sphinx-theme - # via sphinx-autodoc2 - # via sqlalchemy -uc-micro-py==1.0.3 - # via linkify-it-py -urllib3==2.2.3 - # via requests -virtualenv==20.26.5 - # via pre-commit -wcwidth==0.2.13 - # via prompt-toolkit -wheel==0.44.0 - # via sphinx-togglebutton -xxhash==3.5.0 - # via flogging -zipp==3.20.2 - # via importlib-metadata diff --git a/requirements-lint.txt b/requirements-lint.txt deleted file mode 100644 index 76967067..00000000 --- a/requirements-lint.txt +++ /dev/null @@ -1,14 +0,0 @@ -colorama==0.4.5 -flake8==3.9.2 -flake8-bugbear==21.9.2 -flake8-docstrings==1.6.0 -flake8-import-order==0.18.1 -flake8-quotes==3.3.1 -flake8-commas==2.1.0 -isort==5.10.1 -pylint==2.11.1 -pydocstyle==6.1.1 -pycodestyle==2.7.0 -flakehell==0.9.0 -black==22.8.0 -pre-commit==2.15.0 \ No newline at end of file diff --git a/requirements-test.txt b/requirements-test.txt deleted file mode 100644 index c34c39ca..00000000 --- a/requirements-test.txt +++ /dev/null @@ -1,6 +0,0 @@ -psutil==5.8.0 -pytest==6.2.5 -pytest-cov==4.0.0 -pytest-xdist==2.4.0 -pytest-rerunfailures==10.2 -hypothesis==6.24.6 \ No newline at end of file diff --git a/requirements.lock b/requirements.lock deleted file mode 100644 index b8e68729..00000000 --- a/requirements.lock +++ /dev/null @@ -1,64 +0,0 @@ -# generated by rye -# use `rye lock` or `rye sync` to update this lockfile -# -# last locked with the following flags: -# pre: false -# features: [] -# all-features: true -# with-sources: false -# generate-hashes: false -# universal: true - --e file:. -antlr4-python3-runtime==4.9.3 - # via hydra-core - # via omegaconf -cfgv==3.4.0 - # via pre-commit -click==8.1.7 - # via mloq -colorama==0.4.6 ; platform_system == 'Windows' - # via click -distlib==0.3.8 - # via virtualenv -filelock==3.16.1 - # via virtualenv -flogging==0.0.23 - # via mloq -hydra-core==1.3.2 - # via mloq -identify==2.6.1 - # via pre-commit -invoke==2.2.0 - # via mloq -jinja2==3.1.4 - # via mloq -markupsafe==2.1.5 - # via jinja2 -mypy==1.11.2 - # via mloq -mypy-extensions==1.0.0 - # via mypy -nodeenv==1.9.1 - # via pre-commit -omegaconf==2.3.0 - # via hydra-core -packaging==24.1 - # via hydra-core -param==2.1.1 - # via mloq -platformdirs==4.3.6 - # via virtualenv -pre-commit==3.8.0 - # via mloq -pyyaml==6.0.2 - # via omegaconf - # via pre-commit -tomli==2.0.1 ; python_full_version < '3.11' - # via mypy -typing-extensions==4.12.2 - # via mypy -virtualenv==20.26.5 - # via pre-commit -xxhash==3.5.0 - # via flogging diff --git a/setup.py b/setup.py deleted file mode 100644 index a9bcf70d..00000000 --- a/setup.py +++ /dev/null @@ -1,63 +0,0 @@ -"""mloq package installation metadata.""" -from importlib.machinery import SourceFileLoader -from pathlib import Path - -from setuptools import find_packages, setup - - -version = SourceFileLoader( - "mloq.version", - str(Path(__file__).parent / "src" / "mloq" / "version.py"), -).load_module() - -with open(Path(__file__).with_name("README.md"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="mloq", - description="Package for initializing ML projects following ML Ops best practices.", - long_description=long_description, - long_description_content_type="text/markdown", - packages=find_packages("src"), - package_dir={"": "src"}, - version=version.__version__, - license="MIT", - author="FragileTech", - author_email="guillem@fragile.tech", - url="https://github.com/FragileTech/ml-ops-quickstart", - download_url="https://github.com/FragileTech/ml-ops-quickstart/releases", - keywords=["Machine learning", "artificial intelligence"], - test_suite="tests", - tests_require=["pytest>=5.3.5", "hypothesis>=5.6.0"], - install_requires=[ - "flogging>=0.0.14", - "jinja2>=3.0.0", - "click>=8.0.0", - "invoke>=1.4.1", - "hydra-core>=1.1.1", - "param>=1.11.0", - "pre-commit>=2.15.0", - "typing-extensions>=4.0.0", - ], - package_data={ - "": ["README.md"], - "mloq": ["assets/**/*", "assets/**/.*", "tests/**/*", "tests/**/.*"], - }, - include_package_data=True, - classifiers=[ - "Development Status :: 3 - Alpha", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3 :: Only", - "Topic :: Software Development :: Libraries", - ], - entry_points=""" - [console_scripts] - mloq=mloq.cli:cli - """, -) diff --git a/src/mloq.egg-info/PKG-INFO b/src/mloq.egg-info/PKG-INFO deleted file mode 100644 index 9c9a0995..00000000 --- a/src/mloq.egg-info/PKG-INFO +++ /dev/null @@ -1,86 +0,0 @@ -Metadata-Version: 2.1 -Name: mloq -Version: 0.0.67 -Summary: Package for initializing ML projects following ML Ops best practices. -Home-page: https://github.com/FragileTech/ml-ops-quickstart -Download-URL: https://github.com/FragileTech/ml-ops-quickstart/releases -Author: FragileTech -Author-email: guillem@fragile.tech -License: MIT -Keywords: Machine learning,artificial intelligence -Classifier: Development Status :: 3 - Alpha -Classifier: Environment :: Console -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Topic :: Software Development :: Libraries -Description-Content-Type: text/markdown -License-File: LICENSE - -# ML Ops Quickstart -[![Documentation Status](https://readthedocs.org/projects/mloq/badge/?version=latest)](https://mloq.readthedocs.io/en/latest/?badge=latest) -[![Code coverage](https://codecov.io/github/fragiletech/ml-ops-quickstart/coverage.svg)](https://codecov.io/github/fragiletech/ml-ops-quickstart) -[![PyPI package](https://badgen.net/pypi/v/mloq)](https://pypi.org/project/mloq/) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) -[![license: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) - -ML Ops Quickstart is a tool for initializing Machine Learning projects following ML Ops best practices. - -Setting up new repositories is a time-consuming task that involves creating different files and -configuring tools such as linters, docker containers and continuous integration pipelines. -The goal of `mloq` is to simplify that process, so you can start writing code as fast as possible. - -`mloq` generates customized templates for Python projects with focus on Maching Learning. An example of -the generated templates can be found in [mloq-template](https://github.com/FragileTech/mloq-template). - -## [1.](#Index) Installation - -`mloq` is tested on Ubuntu 18.04+, and supports Python 3.6+. - -### Install from pypi -```bash -pip install mloq -``` -### Install from source -```bash -git clone https://github.com/FragileTech/ml-ops-quickstart.git -cd ml-ops-quickstart -pip install -e . -``` - -## [2.](#Index) Usage -### [2.1](#Index) Command line interface - -Options: -* `--file` `-f`: Name of the configuration file. If `file` it's a directory it will load the `mloq.yml` file present in it. - -* `--overwrite` `-o`: Rewrite files that already exist in the target project. -* `--interactive` `-i`: Missing configuration data can be defined interactively from the CLI. - -#### Usage examples -Arguments: -* `OUTPUT_DIRECTORY`: Path to the target project. - -To set up a new repository from scratch interactively in the curren working directory: -```bash -mloq setup -i . -``` - -To load a `mloq.yml` configuration file from the current repository, and initialize the directory `example`, and -overwrite all existing files with no interactivity: -```bash -mloq setup -f . -o example -``` - -![ci python](docs/images/mloq_setup.png) - -## [5.](#Index) License -ML Ops Quickstart is released under the [MIT](LICENSE) license. - -## [6.](#Index) Contributing - -Contributions are very welcome! Please check the [contributing guidelines](CONTRIBUTING.md) before opening a pull request. diff --git a/src/mloq.egg-info/SOURCES.txt b/src/mloq.egg-info/SOURCES.txt deleted file mode 100644 index 7be7c6c0..00000000 --- a/src/mloq.egg-info/SOURCES.txt +++ /dev/null @@ -1,77 +0,0 @@ -LICENSE -README.md -pyproject.toml -setup.py -src/mloq/__init__.py -src/mloq/__main__.py -src/mloq/_utils.py -src/mloq/cli.py -src/mloq/command.py -src/mloq/custom_click.py -src/mloq/failure.py -src/mloq/files.py -src/mloq/git.py -src/mloq/record.py -src/mloq/runner.py -src/mloq/templating.py -src/mloq/version.py -src/mloq/writer.py -src/mloq.egg-info/PKG-INFO -src/mloq.egg-info/SOURCES.txt -src/mloq.egg-info/dependency_links.txt -src/mloq.egg-info/entry_points.txt -src/mloq.egg-info/requires.txt -src/mloq.egg-info/top_level.txt -src/mloq/assets/docker/Dockerfile -src/mloq/assets/docker/Makefile.docker -src/mloq/assets/requirements/data-science.txt -src/mloq/assets/requirements/data-visualization.txt -src/mloq/assets/requirements/dogfood.txt -src/mloq/assets/requirements/pytorch.txt -src/mloq/assets/requirements/requirements-lint.txt -src/mloq/assets/requirements/requirements-test.txt -src/mloq/assets/requirements/requirements.txt -src/mloq/assets/requirements/tensorflow.txt -src/mloq/assets/templates/.codecov.yml -src/mloq/assets/templates/.gitignore -src/mloq/assets/templates/.pre-commit-config.yaml -src/mloq/assets/templates/APACHE_LICENSE -src/mloq/assets/templates/CODE_OF_CONDUCT.md -src/mloq/assets/templates/CONTRIBUTING.md -src/mloq/assets/templates/DCO.md -src/mloq/assets/templates/GPL_LICENSE -src/mloq/assets/templates/MIT_LICENSE -src/mloq/assets/templates/MLproject -src/mloq/assets/templates/Makefile -src/mloq/assets/templates/README.md -src/mloq/assets/templates/WHAT_MLOQ_GENERATED.md -src/mloq/assets/templates/init.txt -src/mloq/assets/templates/main.txt -src/mloq/assets/templates/mloq.yaml -src/mloq/assets/templates/push.yml -src/mloq/assets/templates/pyproject.toml -src/mloq/assets/templates/setup.txt -src/mloq/assets/templates/test_main.txt -src/mloq/assets/templates/version.txt -src/mloq/assets/templates/docs/conf.txt -src/mloq/assets/templates/docs/deploy-docs.yml -src/mloq/assets/templates/docs/index.md -src/mloq/assets/templates/docs/make_bat.txt -src/mloq/assets/templates/docs/makefile_docs.txt -src/mloq/assets/templates/docs/requirements-docs.txt -src/mloq/commands/__init__.py -src/mloq/commands/ci.py -src/mloq/commands/docker.py -src/mloq/commands/docs.py -src/mloq/commands/globals.py -src/mloq/commands/license.py -src/mloq/commands/lint.py -src/mloq/commands/package.py -src/mloq/commands/project.py -src/mloq/commands/requirements.py -src/mloq/commands/setup.py -src/mloq/config/__init__.py -src/mloq/config/configuration.py -src/mloq/config/custom_click.py -src/mloq/config/param_patch.py -src/mloq/config/prompt.py \ No newline at end of file diff --git a/src/mloq.egg-info/dependency_links.txt b/src/mloq.egg-info/dependency_links.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/mloq.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/mloq.egg-info/entry_points.txt b/src/mloq.egg-info/entry_points.txt deleted file mode 100644 index ef30d61a..00000000 --- a/src/mloq.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -mloq = mloq.cli:cli diff --git a/src/mloq.egg-info/requires.txt b/src/mloq.egg-info/requires.txt deleted file mode 100644 index 3ecc53a5..00000000 --- a/src/mloq.egg-info/requires.txt +++ /dev/null @@ -1,8 +0,0 @@ -flogging>=0.0.14 -jinja2>=3.0.0 -click>=8.0.0 -invoke>=1.4.1 -hydra-core>=1.1.1 -param>=1.11.0 -pre-commit>=2.15.0 -typing-extensions>=4.0.0 diff --git a/src/mloq.egg-info/top_level.txt b/src/mloq.egg-info/top_level.txt deleted file mode 100644 index 93d5543f..00000000 --- a/src/mloq.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -mloq diff --git a/src/mloq/__init__.py b/src/mloq/__init__.py index ed12e807..693f041b 100644 --- a/src/mloq/__init__.py +++ b/src/mloq/__init__.py @@ -1,11 +1,7 @@ -"""Package for initializing ML projects following ML Ops best practices.""" -from datetime import date -import logging +__version__ = "0.1.0" -import flogging -from omegaconf import OmegaConf +from .core import compute - -flogging.setup() -_logger = logging.getLogger("mloq") -OmegaConf.register_new_resolver("current_year", lambda: date.today().year) +__all__ = [ + "compute", +] diff --git a/src/mloq/__main__.py b/src/mloq/__main__.py index 8e67e3e1..416f8755 100644 --- a/src/mloq/__main__.py +++ b/src/mloq/__main__.py @@ -1,8 +1,15 @@ -"""Command line interface for mloq.""" -import sys +"""Entrypoint module, in case you use `python -m mloq`. + +Why does this file exist, and why __main__? For more info, read: -from mloq.cli import cli +- https://www.python.org/dev/peps/pep-0338/ +- https://docs.python.org/2/using/cmdline.html#cmdoption-m +- https://docs.python.org/3/using/cmdline.html#cmdoption-m +""" + +import sys +from mloq.cli import run if __name__ == "__main__": - sys.exit(cli()) + sys.exit(run()) diff --git a/src/mloq/_utils.py b/src/mloq/_utils.py deleted file mode 100644 index 4fd6d50a..00000000 --- a/src/mloq/_utils.py +++ /dev/null @@ -1,133 +0,0 @@ -"""This module contains some utilities that are not currently used.""" -import filecmp -import os -from pathlib import Path -from typing import List, Union - -from omegaconf import DictConfig, OmegaConf - -from mloq.files import mloq_yml - - -# TODO: Fix docs -def dir_trees_are_equal(dir1: Union[str, Path], dir2: Union[str, Path]) -> bool: - """ - Compare two directories recursively. Files in each directory are \ - assumed to be equal if their names and contents are equal. - - @param dir1: First directory path - @param dir2: Second directory path - - @return: True if the directory trees are the same and - there were no errors while accessing the directories or files, - False otherwise. - """ - dirs_cmp = filecmp.dircmp(dir1, dir2) - if ( - len(dirs_cmp.left_only) > 0 - or len(dirs_cmp.right_only) > 0 - or len(dirs_cmp.funny_files) > 0 - ): - return False - (_, mismatch, errors) = filecmp.cmpfiles(dir1, dir2, dirs_cmp.common_files, shallow=False) - if len(mismatch) > 0 or len(errors) > 0: - return False - for common_dir in dirs_cmp.common_dirs: - new_dir1 = os.path.join(dir1, common_dir) - new_dir2 = os.path.join(dir2, common_dir) - if not dir_trees_are_equal(new_dir1, new_dir2): - return False - return True - - -def files_are_equal(path1: Union[str, Path], path2: Union[str, Path]) -> bool: - """ - Compare the content of two files. - - Compare two incoming files. They are assumed equal if their contents - are the same. - - Args: - path1: Path containing the first file to be compared. - path2: Path containing the second file to be compared. - - Return: - It returns True if the two given files are equal and no errors - have arisen during the process. False otherwise. - """ - path1 = Path(path1) if isinstance(path1, str) else path1 - path2 = Path(path2) if isinstance(path2, str) else path2 - return filecmp.cmp(path1, path2, shallow=False) - - -def get_generated_files(path: Union[str, Path]) -> List[Path]: - """ - List all the files generated in the last mloq run. - - Args: - path: path to WHAT_MLOQ_GENERATED.md file. - - Returns: - List of Path containing the names of the files generated by mloq. - """ - # Read file as string - # Split file to end up with the desired file names - pass - - -# TODO: Improve the Ledger class so it can also track the different directories -# that were created, and add them under a new section in the WHAT_MLOQ_GENERATED.md - - -def get_generated_directories(path: Union[str, Path]) -> List[Path]: - """ - List all the directories generated in the last mloq run. - - Args: - path: path to WHAT_MLOQ_GENERATED.md file. - - Returns: - List of Path containing the names of the directories generated by mloq. - """ - pass - - -def check_directories_exist(paths: List[Union[str, Path]]) -> bool: - """ - Check if the provided paths exist. - - Args: - paths: List of paths that will be checked - - Returns: - True if all the provided paths exist. False otherwise. - """ - for path in paths: - path = Path(path) if isinstance(path, str) else path - cond1 = os.path.exists(path) - cond2 = os.path.isfile(path) or os.path.isdir(path) - cond3 = Path.exists(path) - if not (cond1 and cond2 and cond3): - print(f"The given path {path} is neither a file nor a directory.") - return cond1 and cond2 and cond3 - - -def get_docker_python_version(template: DictConfig) -> str: - """Return the highest python version defined for the project.""" - max_version = list(sorted(template["python_versions"]))[-1] - version = max_version.replace(".", "") - return f"py{version}" - - -def write_config_setup(config: DictConfig, path: Union[Path, str], safe: bool = False): - """Write setup config in a yaml file.""" - if safe: - path = Path(path) - path = path / mloq_yml.dst if path.is_dir() else path - with open(path, "w") as f: - OmegaConf.save(config, f) - - -def load_empty_config_setup() -> DictConfig: - """Return a dictionary containing all the MLOQ setup config values set to None.""" - return OmegaConf.load(mloq_yml.src) diff --git a/src/mloq/assets/ci/push.yml b/src/mloq/assets/ci/push.yml deleted file mode 100644 index f68a25a5..00000000 --- a/src/mloq/assets/ci/push.yml +++ /dev/null @@ -1,259 +0,0 @@ -name: Push - -on: - push: - branches: - - {{ci.default_branch}} - pull_request: - branches: - - {{ci.default_branch}} - -env: - PROJECT_NAME: {{ci.project_name}} - PROJECT_DIR: src/{{ci.project_name.replace("-", "_")}} - VERSION_FILE: src/{{ci.project_name.replace("-", "_")}}/version.py - DEFAULT_BRANCH: {{ci.default_branch}} - BOT_NAME: {{ci.bot_name}} - BOT_EMAIL: {{ci.bot_email}} - DOCKER_ORG: {{ci.docker_org}} - PIP_CACHE: | - ~/.cache/pip - ~/.local/bin - ~/.local/lib/python3.*/site-packages - -jobs: - style-check: - name: Style check - if: "!contains(github.event.head_commit.message, 'Bump version')" - runs-on: {{ ci.ubuntu_version }} - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python {{ ci.ci_python_version }} - uses: actions/setup-python@v2 - with: - python-version: "{{ ci.ci_python_version }}" - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{'{{'}} env.PIP_CACHE {{'}}'}} - key: {{ ci.ubuntu_version }}-pip-lint-${{'{{'}} hashFiles('requirements-lint.txt') {{'}}'}} - restore-keys: {{ ci.ubuntu_version }}-pip-lint- - - name: Install lint dependencies - run: | - set -x - pip install -r requirements-lint.txt - - - name: Run style check and linter - run: | - set -x - make check - - pytest: - name: Run Pytest - runs-on: {{ ci.ubuntu_version }} - if: "!contains(github.event.head_commit.message, 'Bump version')" - strategy: - matrix: - python-version: {{ ci.python_versions }} - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python ${{"{{"}} matrix.python-version {{"}}"}} - uses: actions/setup-python@v2 - with: - python-version: ${{"{{"}} matrix.python-version {{"}}"}} - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{'{{'}} env.PIP_CACHE {{'}}'}} - key: {{ ci.ubuntu_version }}-pip-test-${{"{{"}} matrix.python-version {{"}}"}}-${{'{{'}} hashFiles('requirements.txt', 'requirements-test.txt') {{'}}'}} - restore-keys: {{ ci.ubuntu_version }}-pip-test- - - name: Install test and package dependencies - run: | - set -x - pip install -r requirements-test.txt -r requirements.txt - pip install .{% if ci.ci_extra %} - - name: Additional setup - run: | - set -x - {{ ci.ci_extra | indent(8) }}{% endif %} - - - name: Test with pytest - run: | - set -x - make test-codecov - - - name: Upload coverage report - if: ${{"{{"}} matrix.python-version=='{{ ci.ci_python_version }}' {{"}}"}} - uses: codecov/codecov-action@v3 -{% if ci.docker %} - test-docker: - name: Test Docker container - runs-on: {{ ci.ubuntu_version }} - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - uses: actions/checkout@v2 - - name: Build container - run: | - set -x - make docker-build - - name: Run tests - run: | - set -x - make docker-test -{% endif %} - build-test-package: - name: Build and test the package - needs: style-check - runs-on: {{ ci.ubuntu_version }} - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python {{ ci.ci_python_version }} - uses: actions/setup-python@v2 - with: - python-version: {{ ci.ci_python_version }} - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{'{{'}} env.PIP_CACHE {{'}}'}} - key: {{ ci.ubuntu_version }}-pip-test-{{ ci.ci_python_version }}-${{'{{'}} hashFiles('requirements.txt', 'requirements-test.txt') {{'}}'}} - restore-keys: {{ ci.ubuntu_version }}-pip-test- - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel bump2version - - - name: Create unique version for test.pypi - run: | - set -x - current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2) - ts=$(date +%s) - new_version="$current_version$ts" - bumpversion --current-version $current_version --new-version $new_version patch $VERSION_FILE - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to TestPyPI - env: - TEST_PYPI_PASS: ${{'{{'}} secrets.TEST_PYPI_PASS {{'}}'}} - if: "'$TEST_PYPI_PASS' != ''" - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{'{{'}} secrets.TEST_PYPI_PASS {{'}}'}} - repository_url: https://test.pypi.org/legacy/ - skip_existing: true - - - name: Install dependencies - run: | - set -x - python -m pip install dist/*.whl -r requirements-test.txt{% if ci.ci_extra %} - - name: Additional setup - run: | - set -x - {{ ci.ci_extra | indent(10) }}{% endif %} - - - name: Test package - run: | - set -x - rm -rf $PROJECT_DIR - make test - - bump-version: - name: Bump package version - env: - BOT_AUTH_TOKEN: ${{'{{'}} secrets.BOT_AUTH_TOKEN {{'}}'}} - if: "!contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/{{ci.default_branch}}' && '$BOT_AUTH_TOKEN' != ''" - runs-on: {{ ci.ubuntu_version }} - needs: - - pytest - - build-test-package{% if ci.docker %} - - test-docker{% endif %} - steps: - - name: actions/checkout - uses: actions/checkout@v2 - with: - persist-credentials: false - fetch-depth: 100 - - name: current_version - run: | - set -x - echo "current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2)" >> $GITHUB_ENV - echo "version_file=$VERSION_FILE" >> $GITHUB_ENV - echo 'bot_name="${BOT_NAME}"' >> $GITHUB_ENV - echo 'bot_email="${BOT_EMAIL}"' >> $GITHUB_ENV - - name: FragileTech/bump-version - uses: FragileTech/bump-version@main - with: - current_version: "${{'{{'}} env.current_version {{'}}'}}" - files: "${{'{{'}} env.version_file {{'}}'}}" - commit_name: "${{'{{'}} env.bot_name {{'}}'}}" - commit_email: "${{'{{'}} env.bot_email {{'}}'}}" - login: "${{'{{'}} env.bot_name {{'}}'}}" - token: "${{'{{'}} secrets.BOT_AUTH_TOKEN {{'}}'}}" -{% if ci.docker %} - push-docker: - name: Push Docker container - runs-on: {{ ci.ubuntu_version }} - env: - DOCKERHUB_PASS: ${{'{{'}} secrets.DOCKERHUB_PASS {{'}}'}} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/{{ci.default_branch}}' && '$DOCKERHUB_PASS' != ''" - steps: - - uses: actions/checkout@v2 - - name: Login to DockerHub - run: | - set -x - docker login -u "${{'{{'}} secrets.DOCKERHUB_LOGIN {{'}}'}}" -p "${{'{{'}} secrets.DOCKERHUB_PASS {{'}}'}}" docker.io - - - name: Build container - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-build VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG - - name: Push images - - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-push VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG -{% endif %} - release-package: - name: Release PyPI package - env: - PYPI_PASS: ${{'{{'}} secrets.PYPI_PASS {{'}}'}} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/{{ci.default_branch}}' && '$PYPI_PASS' != ''" - runs-on: {{ ci.ubuntu_version }} - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python {{ ci.ci_python_version }} - uses: actions/setup-python@v2 - with: - python-version: {{ ci.ci_python_version }} - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{"{{"}} secrets.PYPI_PASS {{"}}"}} diff --git a/src/mloq/assets/docker/Dockerfile b/src/mloq/assets/docker/Dockerfile deleted file mode 100644 index c64997e9..00000000 --- a/src/mloq/assets/docker/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM {{ docker.base_image }} -{% if docker.jupyter %}ARG JUPYTER_PASSWORD="{{docker.jupyter_password}}"{% endif %} -ENV BROWSER=/browser \ - LC_ALL=en_US.UTF-8 \ - LANG=en_US.UTF-8 -COPY Makefile.docker Makefile -COPY . {{ docker.project_name }}/ - -RUN apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends make cmake && \ - make install-python{{docker.python_version}} && \ - make install-common-dependencies && \ - make install-python-libs - -RUN cd {{docker.project_name}} \ - && python3 -m pip install -U pip \ - {% if docker.lint %}&& pip3 install -r requirements-lint.txt \{% endif %} - {% if docker.test %}&& pip3 install -r requirements-test.txt \{% endif %} - && pip3 install -r requirements.txt \ - {% if docker.jupyter %}&& pip3 install ipython jupyter \{% endif %} - && pip3 install -e . \ - && git config --global init.defaultBranch master \ - && git config --global user.name "Whoever" \ - && git config --global user.email "whoever@fragile.tech" -{{ docker.extra }} -RUN make remove-dev-packages - -{% if docker.jupyter %}RUN mkdir /root/.jupyter && \ - echo 'c.NotebookApp.token = "'${JUPYTER_PASSWORD}'"' > /root/.jupyter/jupyter_notebook_config.py -CMD jupyter notebook --allow-root --port 8080 --ip 0.0.0.0 -{% endif %} \ No newline at end of file diff --git a/src/mloq/assets/docker/Makefile.docker b/src/mloq/assets/docker/Makefile.docker deleted file mode 100644 index 67b30b5a..00000000 --- a/src/mloq/assets/docker/Makefile.docker +++ /dev/null @@ -1,79 +0,0 @@ -current_dir = $(shell pwd) - -PROJECT = dockerfiles -VERSION ?= latest -DOCKER_TAG = None -PYTHON_VERSION = "3.8" -UBUNTU_NAME = $(lsb_release -s -c) - -# Install system packages -.PHONY: install-common-dependencies -install-common-dependencies: - apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends \ - ca-certificates locales pkg-config apt-utils gcc g++ wget make cmake git curl flex ssh gpgv \ - libffi-dev libjpeg-turbo-progs libjpeg8-dev libjpeg-turbo8 libjpeg-turbo8-dev gnupg2 \ - libpng-dev libpng16-16 libglib2.0-0 bison gfortran lsb-release \ - libsm6 libxext6 libxrender1 libfontconfig1 libhdf5-dev libopenblas-base libopenblas-dev \ - libfreetype6 libfreetype6-dev zlib1g-dev zlib1g xvfb python-opengl ffmpeg libhdf5-dev && \ - ln -s /usr/lib/x86_64-linux-gnu/libz.so /lib/ && \ - ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /lib/ && \ - echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ - locale-gen && \ - wget -O - https://bootstrap.pypa.io/get-pip.py | python3 && \ - rm -rf /var/lib/apt/lists/* && \ - echo '#!/bin/bash\n\\n\echo\n\echo " $@"\n\echo\n\' > /browser && \ - chmod +x /browser - - -.PHONY: remove-dev-packages -remove-dev-packages: - pip3 uninstall -y cython && \ - apt-get remove -y cmake pkg-config flex bison curl libpng-dev \ - libjpeg-turbo8-dev zlib1g-dev libhdf5-dev libopenblas-dev gfortran \ - libfreetype6-dev libjpeg8-dev libffi-dev && \ - apt-get autoremove -y && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Install Python 3.9 -.PHONY: install-python3.9 -install-python3.9: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.9 python3.9-dev python3-distutils python3-setuptools - -# Install Python 3.8 -.PHONY: install-python3.8 -install-python3.8: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.8 python3.8-dev python3-distutils python3-setuptools - -# Install Python 3.7 -.PHONY: install-python3.7 -install-python3.7: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.7 python3.7-dev python3-distutils python3-setuptools - -# Install Python 3.6 -.PHONY: install-python3.6 -install-python3.6: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.6 python3.6-dev python3-distutils python3-setuptools \ - -# Install phantomjs for holoviews image save -.PHONY: install-phantomjs -install-phantomjs: - curl -sSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ - echo "deb https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee /etc/apt/sources.list.d/nodesource.list && \ - echo "deb-src https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee -a /etc/apt/sources.list.d/nodesource.list && \ - apt-get update && apt-get install -y nodejs && \ - npm install phantomjs --unsafe-perm && \ - npm install -g phantomjs-prebuilt --unsafe-perm - -# Install common python dependencies -.PHONY: install-python-libs -install-python-libs: - python3 -m pip install -U pip && \ - pip3 install --no-cache-dir setuptools wheel cython pipenv && \ - pip3 install --no-cache-dir matplotlib && \ - python3 -c "import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot" diff --git a/src/mloq/assets/docs/conf.txt b/src/mloq/assets/docs/conf.txt deleted file mode 100644 index 3039c19e..00000000 --- a/src/mloq/assets/docs/conf.txt +++ /dev/null @@ -1,140 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys - - -sys.path.insert(0, os.path.abspath("../../")) -sys.setrecursionlimit(1500) - -# -- Project information ----------------------------------------------------- -project = "{{docs.project_name}}" -copyright = "{{docs.copyright_year}}, {{docs.copyright_holder}}" -author = "{{docs.author}}" - -# The short X.Y version -from {{docs.project_name}}.version import __version__ - - -version = __version__ -# The full version, including alpha/beta/rc tags -release = __version__ -# -- General configuration --------------------------------------------------- -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ["_build", "**.ipynb_checkpoints"] -# The master toctree document. -master_doc = "index" -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - #"sphinx.ext.autodoc", - "autoapi.extension", - "sphinx.ext.doctest", - "sphinx.ext.intersphinx", - "sphinx.ext.todo", - "sphinx.ext.coverage", - "sphinx.ext.imgmath", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx.ext.autosectionlabel", - "sphinx.ext.autodoc.typehints", - "sphinx_book_theme", - "myst_nb", - "sphinxcontrib.mermaid", - "sphinx.ext.githubpages", -] -suppress_warnings = ["image.nonlocal_uri"] -autodoc_typehints = "description" -# Autoapi settings -autoapi_type = "python" -autoapi_dirs = ["../../src/{{docs.project_name}}"] -autoapi_add_toctree_entry = True -# Make use of custom templates -autoapi_template_dir = "_autoapi_templates" -exclude_patterns.append("_autoapi_templates/index.rst") - -# Ignore sphinx-autoapi warnings on multiple target description -suppress_warnings.append("ref.python") - -# Napoleon settings -napoleon_google_docstring = True -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = True -napoleon_include_private_with_doc = False -napoleon_include_special_with_doc = True -napoleon_use_admonition_for_examples = False -napoleon_use_admonition_for_notes = False -napoleon_use_admonition_for_references = False -napoleon_use_ivar = False -napoleon_use_param = True -napoleon_use_rtype = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_title = "" -html_theme = "sphinx_book_theme" -# html_logo = "_static/logo-wide.svg" -# html_favicon = "_static/logo-square.svg" -html_theme_options = { - "github_url": "{{docs.project_url}}", - "repository_url": "{{docs.project_url}}", - "repository_branch": "gh-pages", - "home_page_in_toc": True, - "path_to_docs": "docs", - "show_navbar_depth": 1, - "use_edit_page_button": True, - "use_repository_button": True, - "use_download_button": True, - "launch_buttons": { - "binderhub_url": "https://mybinder.org", - "notebook_interface": "classic", - }, -} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# myst_parser options -myst_heading_anchors = 2 -myst_enable_extensions = [ - "amsmath", - "colon_fence", - "deflist", - "dollarmath", - "html_admonition", - "html_image", - "linkify", - "replacements", - "smartquotes", - "substitution", -] - - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True diff --git a/src/mloq/assets/docs/deploy-docs.yml b/src/mloq/assets/docs/deploy-docs.yml deleted file mode 100644 index 231815a9..00000000 --- a/src/mloq/assets/docs/deploy-docs.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Pages -on: - push: - branches: - - {{docs.default_branch}} - -env: - NOTEBOOKS_SRC_DIR: "../notebooks" - NOTEBOOKS_BUILD_DIR: "./source/notebooks" - PIP_CACHE: | - ~/.cache/pip - ~/.local/bin - ~/.local/lib/python3.*/site-packages - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/setup-python@v2 - with: - python-version: "3.8" - - uses: actions/checkout@master - with: - fetch-depth: 0 # otherwise, you will failed to push refs to dest repo - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{'{{'}} env.PIP_CACHE {{'}}'}} - key: ubuntu-20.04-pip-docs-${{'{{'}} hashFiles('requirements.txt') {{'}}'}} - restore-keys: ubuntu-20.04-pip-docs- - - name: Install package and dependencies - run: | - set -x - pip install -r requirements.txt - pip install . - if [ -e "${NOTEBOOKS_SRC_DIR}" ] && [ -e "${NOTEBOOKS_BUILD_DIR}" ]; then \ - echo "${NOTEBOOKS_BUILD_DIR} Updating notebook folder."; \ - rm -rf "${NOTEBOOKS_BUILD_DIR}"; \ - cp -r "${NOTEBOOKS_SRC_DIR}" "${NOTEBOOKS_BUILD_DIR}"; \ - fi - - name: Build and Commit - uses: sphinx-notes/pages@v2 - with: - documentation_path: ./docs/source - requirements_path: ./docs/requirements-docs.txt - - name: Push changes - uses: ad-m/github-push-action@master - with: - github_token: ${{'{{'}} secrets.GITHUB_TOKEN {{'}}'}} - branch: gh-pages \ No newline at end of file diff --git a/src/mloq/assets/docs/index.md b/src/mloq/assets/docs/index.md deleted file mode 100644 index d2c6a885..00000000 --- a/src/mloq/assets/docs/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Welcome to {{docs.project_name}} - -{{docs.description}} - -```{toctree} ---- -maxdepth: 5 -caption: {{docs.project_name}} API ---- -autoapi/index.rst -``` \ No newline at end of file diff --git a/src/mloq/assets/docs/make_bat.txt b/src/mloq/assets/docs/make_bat.txt deleted file mode 100644 index f721cff8..00000000 --- a/src/mloq/assets/docs/make_bat.txt +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/src/mloq/assets/docs/makefile_docs.txt b/src/mloq/assets/docs/makefile_docs.txt deleted file mode 100644 index a159fb71..00000000 --- a/src/mloq/assets/docs/makefile_docs.txt +++ /dev/null @@ -1,40 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build -NOTEBOOKS_SRC_DIR = ../notebooks -NOTEBOOKS_BUILD_DIR = ./source/notebooks - - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: server -server: - python3 -m http.server --directory build/html/ - -# rm -rf source/notebooks -# cp -r ../notebooks ./source/notebooks - -.PHONY: test -test: - if [ -e "${NOTEBOOKS_SRC_DIR}" ] && [ -e "${NOTEBOOKS_BUILD_DIR}" ]; then \ - echo "${NOTEBOOKS_BUILD_DIR} Updating notebook folder."; \ - rm -rf "${NOTEBOOKS_BUILD_DIR}"; \ - cp -r "${NOTEBOOKS_SRC_DIR}" "${NOTEBOOKS_BUILD_DIR}"; \ - fi - make html - make server \ No newline at end of file diff --git a/src/mloq/assets/docs/requirements-docs.txt b/src/mloq/assets/docs/requirements-docs.txt deleted file mode 100644 index 85a301f3..00000000 --- a/src/mloq/assets/docs/requirements-docs.txt +++ /dev/null @@ -1,10 +0,0 @@ -sphinx==4.5.0 -linkify-it-py==2.0.0 -myst-parser==0.18.0 -myst-nb==0.16.0 -ruyaml==0.19.2 -sphinx-autoapi==1.9.0 -pydata-sphinx-theme==0.8.1 -sphinxcontrib-mermaid==0.7.1 -sphinx_book_theme==0.3.3 -jupyter-cache==0.5.0 \ No newline at end of file diff --git a/src/mloq/assets/license/APACHE_LICENSE b/src/mloq/assets/license/APACHE_LICENSE deleted file mode 100644 index dc795e22..00000000 --- a/src/mloq/assets/license/APACHE_LICENSE +++ /dev/null @@ -1,202 +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 {{license.copyright_year }} - {{ now().year }} {{license.copyright_holder}} - - 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. \ No newline at end of file diff --git a/src/mloq/assets/license/DCO.md b/src/mloq/assets/license/DCO.md deleted file mode 100644 index e440da92..00000000 --- a/src/mloq/assets/license/DCO.md +++ /dev/null @@ -1,36 +0,0 @@ -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -660 York Street, Suite 102, -San Francisco, CA 94110 USA - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with -this project or the open source license(s) involved. diff --git a/src/mloq/assets/license/GPL_LICENSE b/src/mloq/assets/license/GPL_LICENSE deleted file mode 100644 index e72bfdda..00000000 --- a/src/mloq/assets/license/GPL_LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. \ No newline at end of file diff --git a/src/mloq/assets/license/MIT_LICENSE b/src/mloq/assets/license/MIT_LICENSE deleted file mode 100644 index 6b39c44e..00000000 --- a/src/mloq/assets/license/MIT_LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) {{license.copyright_year}} - {{now().year}} {{license.copyright_holder}} - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/mloq/assets/lint/requirements-lint.txt b/src/mloq/assets/lint/requirements-lint.txt deleted file mode 100644 index 76967067..00000000 --- a/src/mloq/assets/lint/requirements-lint.txt +++ /dev/null @@ -1,14 +0,0 @@ -colorama==0.4.5 -flake8==3.9.2 -flake8-bugbear==21.9.2 -flake8-docstrings==1.6.0 -flake8-import-order==0.18.1 -flake8-quotes==3.3.1 -flake8-commas==2.1.0 -isort==5.10.1 -pylint==2.11.1 -pydocstyle==6.1.1 -pycodestyle==2.7.0 -flakehell==0.9.0 -black==22.8.0 -pre-commit==2.15.0 \ No newline at end of file diff --git a/src/mloq/assets/mloq/WHAT_MLOQ_GENERATED.md b/src/mloq/assets/mloq/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index 1e21d6b0..00000000 --- a/src/mloq/assets/mloq/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,4 +0,0 @@ -# What mloq generated for your project - -{% for file, description in generated_files %}* `{{ file }}` - {{ description }} -{% endfor %} \ No newline at end of file diff --git a/src/mloq/assets/mloq/mloq.yaml b/src/mloq/assets/mloq/mloq.yaml deleted file mode 100644 index 0197f6f1..00000000 --- a/src/mloq/assets/mloq/mloq.yaml +++ /dev/null @@ -1,117 +0,0 @@ -globals: - project_name: ??? - owner: ??? - author: ??? - email: ??? - description: ??? - open_source: ??? - project_url: ??? - default_branch: main - use_poetry: False - license: "proprietary" - main_python_version: "3.8" - -license: - disable: false - license: ${globals.license} - copyright_year: ${current_year:} - copyright_holder: ${globals.owner} - project_name: ${globals.project_name} - project_url: ${globals.project_url} - email: ${globals.email} - -project: - disable: false - license: ${globals.license} - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - tests: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${globals.license} - use_poetry: ${globals.use_poetry} - main_python_version: ${globals.main_python_version} - python_versions: - - '3.7' - - '3.8' - - '3.9' - - '3.10' - pyproject_extra: "" - license_classifier: ??? - -requirements: - disable: false - requirements: - - None - -lint: - disable: false - docstring_checks: true - pyproject_extra: "" - project_name: ${globals.project_name} - black: True - isort: True - linters: True - poetry_requirements: True - makefile: true - -docker: - disable: false - cuda: ??? - cuda_image_type: "cudnn8-runtime" - python_version: "3.8" - ubuntu_version: "20.04" - base_image: "???" - test: true - lint: true - jupyter: true - jupyter_password: ${globals.project_name} - project_name: ${globals.project_name} - docker_org: ${globals.owner} - requirements: ${requirements.requirements} - makefile: true - extra: "" - -ci: - disable: false - bot_name: ??? - bot_email: ??? - docker: ??? - vendor: github - python_versions: ${package.python_versions} - ubuntu_version: ubuntu-18.04 - ci_python_version: '3.8' - ci_extra: "" - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: ${docker.docker_org} \ No newline at end of file diff --git a/src/mloq/assets/package/setup.txt b/src/mloq/assets/package/setup.txt deleted file mode 100644 index 61f9cf48..00000000 --- a/src/mloq/assets/package/setup.txt +++ /dev/null @@ -1,44 +0,0 @@ -"""{{ package.project_name }} package installation metadata.""" -from importlib.machinery import SourceFileLoader -from pathlib import Path - -from setuptools import find_packages, setup - - -version = SourceFileLoader( - "{{ package.project_name }}.version", - str(Path(__file__).parent / "src" / "{{ package.project_name }}" / "version.py"), -).load_module() - -with open(Path(__file__).with_name("README.md"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="{{ package.project_name }}", - description="{{ package.description }}", - long_description=long_description, - long_description_content_type="text/markdown", - packages=find_packages("src"), - package_dir={"": "src"}, - version=version.__version__, - license="{{ package.license }}", - author="{{ package.author }}", - author_email="{{ package.email }}", - url="{{ package.project_url}}", - keywords=["Machine learning", "artificial intelligence"], - test_suite="tests", - tests_require=["pytest>=5.3.5", "hypothesis>=5.6.0"], - extras_require={}, - install_requires=[], - package_data={"": ["README.md"]}, - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "{{ package.license_classifier }}", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Software Development :: Libraries", - ], -) diff --git a/src/mloq/assets/project/.codecov.yml b/src/mloq/assets/project/.codecov.yml deleted file mode 100644 index d9f8fee7..00000000 --- a/src/mloq/assets/project/.codecov.yml +++ /dev/null @@ -1,19 +0,0 @@ -comment: - layout: "reach, diff, flags, files" - behavior: default - require_changes: true # if true: only post the comment if coverage changes - -github_checks: - annotations: true - -ignore: -- tests -coverage: - status: - project: - default: - target: auto - threshold: 0% - informational: true - paths: - - src/{{ project.project_name }} diff --git a/src/mloq/assets/project/.gitignore b/src/mloq/assets/project/.gitignore deleted file mode 100644 index 3e64b337..00000000 --- a/src/mloq/assets/project/.gitignore +++ /dev/null @@ -1,131 +0,0 @@ -# This file is here just for the reference -WHAT_MLOQ_GENERATED.md - -#Mac OS -*.DS_Store - -#PyCharm IDE -.idea/ - -# Documentation build templates -docs/_build/ -docs/build/ - -# Byte-compiled / optimized / DLL templates -**/__pycache__/ -*.py[cod] -*$py.class -*.pyc - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these templates are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docsrc/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed templates -*.sage.py - -# dotenv -.env - -# virtualenv -.venv -venv/ -ENV/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ - -# CI -.ci - -# Notebooks by default are ignored -*.ipynb - -# lock files -*.lock - -# hydra command history -outputs/ - -*.pck -*.npy diff --git a/src/mloq/assets/project/.pre-commit-config.yaml b/src/mloq/assets/project/.pre-commit-config.yaml deleted file mode 100644 index 98e5a947..00000000 --- a/src/mloq/assets/project/.pre-commit-config.yaml +++ /dev/null @@ -1,10 +0,0 @@ -repos: -- repo: https://github.com/psf/black - rev: 20.8b1 - hooks: - - id: black -- repo: https://github.com/PyCQA/isort - rev: 5.8.0 - hooks: - - id: isort - args: ["."] diff --git a/src/mloq/assets/project/CODE_OF_CONDUCT.md b/src/mloq/assets/project/CODE_OF_CONDUCT.md deleted file mode 100644 index 16885242..00000000 --- a/src/mloq/assets/project/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ - -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at {{license.email}}. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available -at [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/src/mloq/assets/project/CONTRIBUTING.md b/src/mloq/assets/project/CONTRIBUTING.md deleted file mode 100644 index 5c1ddfcd..00000000 --- a/src/mloq/assets/project/CONTRIBUTING.md +++ /dev/null @@ -1,56 +0,0 @@ -# Contributing Guidelines - -{{license.project_name}} is [{{license.license}} licensed](LICENSE) and accepts -contributions via GitHub pull requests. This document outlines some of the -conventions on development workflow, commit message formatting, contact points, -and other resources to make it easier to get your contribution accepted. - -## Certificate of Origin - -By contributing to this project you agree to the [Developer Certificate of -Origin (DCO)](DCO.md). This document was created by the Linux Kernel community and is a -simple statement that you, as a contributor, have the legal right to make the -contribution. - -In order to show your agreement with the DCO you should include at the end of commit message, -the following line: `Signed-off-by: John Doe `, using your real name. - -This can be done easily using the [`-s`](https://github.com/git/git/blob/b2c150d3aa82f6583b9aadfecc5f8fa1c74aca09/Documentation/git-commit.txt#L154-L161) flag on the `git commit`. - - -## Support Channels - -The official support channels, for both users and contributors, are: - -- GitHub [issues]({{ license.project_url }}/issues)* - -*Before opening a new issue or submitting a new pull request, it's helpful to -search the project - it's likely that another user has already reported the -issue you're facing, or it's a known issue that we're already aware of. - - -## How to Contribute - -Pull Requests (PRs) are the main and exclusive way to contribute to the official project. -In order for a PR to be accepted it needs to pass a list of requirements: - -- The CI style check passes (run locally with `make check`). -- Code Coverage does not decrease. -- All the tests pass. -- Python code is formatted according to [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/). -- If the PR is a bug fix, it has to include a new unit test that fails before the patch is merged. -- If the PR is a new feature, it has to come with a suite of unit tests, that tests the new functionality. -- In any case, all the PRs have to pass the personal evaluation of at least one of the [maintainers](MAINTAINERS.md). - - -### Format of the commit message - -The commit summary must start with a capital letter and with a verb in present tense. No dot in the end. - -``` -Add a feature -Remove unused code -Fix a bug -``` - -Every commit details should describe what was changed, under which context and, if applicable, the GitHub issue it relates to. diff --git a/src/mloq/assets/project/init.txt b/src/mloq/assets/project/init.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/mloq/assets/project/main.txt b/src/mloq/assets/project/main.txt deleted file mode 100644 index 0991c284..00000000 --- a/src/mloq/assets/project/main.txt +++ /dev/null @@ -1,11 +0,0 @@ -"""{{ project.project_name }} entry point.""" -import sys - - -def main(): - """Do nothing and always return 0.""" - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/src/mloq/assets/project/requirements-test.txt b/src/mloq/assets/project/requirements-test.txt deleted file mode 100644 index c34c39ca..00000000 --- a/src/mloq/assets/project/requirements-test.txt +++ /dev/null @@ -1,6 +0,0 @@ -psutil==5.8.0 -pytest==6.2.5 -pytest-cov==4.0.0 -pytest-xdist==2.4.0 -pytest-rerunfailures==10.2 -hypothesis==6.24.6 \ No newline at end of file diff --git a/src/mloq/assets/project/test_main.txt b/src/mloq/assets/project/test_main.txt deleted file mode 100644 index cd61581e..00000000 --- a/src/mloq/assets/project/test_main.txt +++ /dev/null @@ -1,5 +0,0 @@ -from {{ project.project_name }}.__main__ import main - - -def test_main(): - assert main() == 0 diff --git a/src/mloq/assets/project/version.txt b/src/mloq/assets/project/version.txt deleted file mode 100644 index 26653102..00000000 --- a/src/mloq/assets/project/version.txt +++ /dev/null @@ -1,2 +0,0 @@ -"""Current version of the project. Do not modify manually.""" -__version__ = "0.0.0" diff --git a/src/mloq/assets/requirements/data-science.txt b/src/mloq/assets/requirements/data-science.txt deleted file mode 100644 index d67e5553..00000000 --- a/src/mloq/assets/requirements/data-science.txt +++ /dev/null @@ -1,8 +0,0 @@ -networkx==2.6.3 -numba==0.54.1 -numpy==1.21.4 -opencv-python==4.5.4.58 -pandas==1.3.4 -pillow-simd==7.0.0.post3 -scipy==1.7.2 -scikit-learn==1.0.1 \ No newline at end of file diff --git a/src/mloq/assets/requirements/data-visualization.txt b/src/mloq/assets/requirements/data-visualization.txt deleted file mode 100644 index 56829e3a..00000000 --- a/src/mloq/assets/requirements/data-visualization.txt +++ /dev/null @@ -1,9 +0,0 @@ -bokeh==2.4.1 -holoviews==1.14.6 -hvplot==0.7.3 -matplotlib==3.5.0 -panel==0.12.4 -param==1.12.0 -plotly==5.4.0 -selenium==3.141.0 -streamz==0.6.3 \ No newline at end of file diff --git a/src/mloq/assets/requirements/dogfood.txt b/src/mloq/assets/requirements/dogfood.txt deleted file mode 100644 index a026ea02..00000000 --- a/src/mloq/assets/requirements/dogfood.txt +++ /dev/null @@ -1,9 +0,0 @@ -click==8.0.3 -flogging==0.0.14 -hydra-core==1.1.1 -invoke==1.6.0 -jinja2==3.0.3 -omegaconf==2.1.1 -param==1.12.0 -pre-commit==2.15.0 -typing-extensions==4.0.0 \ No newline at end of file diff --git a/src/mloq/assets/requirements/pytorch.txt b/src/mloq/assets/requirements/pytorch.txt deleted file mode 100644 index ce0b46de..00000000 --- a/src/mloq/assets/requirements/pytorch.txt +++ /dev/null @@ -1,4 +0,0 @@ -torch==1.10.0 -torchvision==0.11.1 -einops==0.3.2 -pytorch-lightning==1.5.2 \ No newline at end of file diff --git a/src/mloq/assets/requirements/requirements.txt b/src/mloq/assets/requirements/requirements.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/mloq/assets/requirements/tensorflow.txt b/src/mloq/assets/requirements/tensorflow.txt deleted file mode 100644 index 47d08327..00000000 --- a/src/mloq/assets/requirements/tensorflow.txt +++ /dev/null @@ -1 +0,0 @@ -tensorflow==2.7.0 \ No newline at end of file diff --git a/src/mloq/assets/shared/Makefile b/src/mloq/assets/shared/Makefile deleted file mode 100644 index 8e9d4331..00000000 --- a/src/mloq/assets/shared/Makefile +++ /dev/null @@ -1,53 +0,0 @@ -current_dir = $(shell pwd) - -PROJECT = {{project.project_name}} -{% if project.tests %}n ?= auto{% endif %} -{% if docker.makefile %}DOCKER_ORG = {{docker.docker_org}} -DOCKER_TAG ?= ${PROJECT} -VERSION ?= latest{% endif %} - -{% if lint.makefile %}.POSIX: -style: - black . - isort . - -.POSIX: -check: - !(grep -R /tmp tests) - flakehell lint src/${PROJECT} - pylint src/${PROJECT} - black --check src/${PROJECT}{% endif %} -{% if project.tests %} -.PHONY: test -test: - find -name "*.pyc" -delete - pytest -n $n -s -o log_cli=true -o log_cli_level=info - -.PHONY: test-codecov -test-codecov: - find -name "*.pyc" -delete - pytest -n $n -s -o log_cli=true -o log_cli_level=info --cov=./src/{{project.project_name}} --cov-report=xml --cov-config=pyproject.toml{% endif %} -{% if docker.makefile %} -.PHONY: docker-shell -docker-shell: - docker run --rm {% if docker.cuda %}--gpus all {% endif %}-v ${current_dir}:/${PROJECT} --network host -w /${PROJECT} -it ${DOCKER_ORG}/${PROJECT}:${VERSION} bash - -.PHONY: docker-notebook -docker-notebook: - docker run --rm {% if docker.cuda %}--gpus all {% endif %}-v ${current_dir}:/${PROJECT} --network host -w /${PROJECT} -it ${DOCKER_ORG}/${PROJECT}:${VERSION} - -.PHONY: docker-build -docker-build: - docker build --pull -t ${DOCKER_ORG}/${PROJECT}:${VERSION} . - -.PHONY: docker-test -docker-test: - find -name "*.pyc" -delete - docker run --rm --network host -w /${PROJECT} --entrypoint python3 ${DOCKER_ORG}/${PROJECT}:${VERSION} -m pytest -n $n -s -o log_cli=true -o log_cli_level=info - -.PHONY: docker-push -docker-push: - docker push ${DOCKER_ORG}/${DOCKER_TAG}:${VERSION} - docker tag ${DOCKER_ORG}/${DOCKER_TAG}:${VERSION} ${DOCKER_ORG}/${DOCKER_TAG}:latest - docker push ${DOCKER_ORG}/${DOCKER_TAG}:latest -{% endif %} \ No newline at end of file diff --git a/src/mloq/assets/shared/README.md b/src/mloq/assets/shared/README.md deleted file mode 100644 index 83e02947..00000000 --- a/src/mloq/assets/shared/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# {{project.project_name}} -[![Documentation Status](https://readthedocs.org/projects/{{project.project_name}}/badge/?version=latest)](https://{{project.project_name}}.readthedocs.io/en/latest/?badge=latest) -[![Code coverage](https://codecov.io/github/{{project.owner}}/{{project.project_name}}/coverage.svg)](https://codecov.io/github/{{project.owner}}/{{project.project_name}}) -[![PyPI package](https://badgen.net/pypi/v/{{project.project_name}})](https://pypi.org/project/{{project.project_name}}/) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) -[![license: {{project.license}}](https://img.shields.io/badge/license-{{project.license}}-green.svg)](https://opensource.org/licenses/{{project.license}}) - -{{project.description}} \ No newline at end of file diff --git a/src/mloq/assets/shared/pyproject.toml b/src/mloq/assets/shared/pyproject.toml deleted file mode 100644 index 49ceaaf3..00000000 --- a/src/mloq/assets/shared/pyproject.toml +++ /dev/null @@ -1,163 +0,0 @@ -{% if package and package.use_poetry %} -[tool.poetry] -name = "{{ package.project_name }}" -version = "0.0.0" -description = "{{ package.description }}" -authors = [ - "{{ package.author }}", -] -maintainers = ["{{ package.author }}"] -license = "{{ package.license }}" -readme = "README.md" - -packages = [ - { include = "{{ package.project_name }}", from = "src" } -] -include = [ - { path = "tests", format = "sdist" } -] - -homepage = "{{ package.project_url}}" -repository = "{{ package.project_url}}" -documentation = "{{ package.project_url}}" - -keywords = ["Machine learning", "artificial intelligence", "{{ package.project_name }}"] - -classifiers = [ - "Development Status :: 3 - Alpha", - "Topic :: Software Development :: Libraries :: Python Modules" -] - -[tool.poetry.build] -generate-setup-file = true - -# Requirements -[tool.poetry.dependencies] -python = "^{{ package.main_python_version}}" - -[tool.poetry.group.dev.dependencies] -pytest = "^7.1" -pytest-cov = "^3.0" -psutil= "^5.8" -pytest-xdist = "^2.5" -pre-commit = "^2.6" -pytest-rerunfailures= "^10.2" -{% if lint and "pyproject.toml" not in lint.ignore_files and lint.add_requirements%} -[tool.poetry.group.lint.dependencies] -colorama = "^0.4" -flake8 = "^3.9" -flake8-bugbear = "^21.9" -flake8-docstrings = "^1.6" -flake8-import-order = "^0.18" -flake8-quotes = "^3.3" -flake8-commas = "^2.1" -isort = "^5.10" -pylint = "^2.11" -pydocstyle = "^6.1" -pycodestyle = "^2.7" -flakehell = "^0.9" -black = "^22.8" -pre-commit = "^2.15" -{% endif %} -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" -{% elif package %}[build-system] -requires = ["setuptools >= 50.3.2", "wheel >= 0.29.0"] -build-backend = "setuptools.build_meta" -{% endif %} -# Code coverage config -[tool.coverage.run] -branch = true -source = ["src/{{lint.project_name}}"] - -[tool.coverage.report] -exclude_lines =["no cover", - 'raise NotImplementedError', - 'if __name__ == "__main__":'] -ignore_errors = true -omit = ["tests/*"] -{% if lint and "pyproject.toml" not in lint.ignore_files %}{% if lint.black %} -# black is the tool to format the source code -[tool.black] -line-length = 99 -target-version = ['py37', 'py38', 'py39'] -include = '\.pyi?$' -exclude = ''' -/( - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - | venv -)/ -'''{% endif %}{% if lint.isort %} -# isort orders and lints imports -[tool.isort] -profile = "black" -line_length = 99 -multi_line_output = 3 -order_by_type = false -force_alphabetical_sort_within_sections = true -force_sort_within_sections = true -combine_as_imports = true -include_trailing_comma = true -color_output = true -lines_after_imports = 2 -honor_noqa = true -{% endif %} -{% if lint.linters %}# Flakehell config -[tool.flakehell] -# optionally inherit from remote config (or local if you want) -base = "https://raw.githubusercontent.com/life4/flakehell/master/pyproject.toml" -# specify any flake8 options. For example, exclude "example.py": -exclude = [".git", "docs", ".ipynb*", "*.ipynb", ".pytest_cache"] -format = "grouped" # make output nice -max_line_length = 99 # show line of source code in output -show_source = true -inline_quotes='"' -import_order_style = "appnexus" -application_package_names = ["{{lint.project_name}}"] -application_import_names = ["{{lint.project_name}}"] -# Fix AttributeError: 'Namespace' object has no attribute 'extended_default_ignore' -extended_default_ignore=[] - -[tool.flakehell.plugins] -"flake8*" = ["+*"{% if not lint.docstring_checks %}, "-D*"{% endif %}] -pylint = ["+*"{% if not lint.docstring_checks %}, "-D*"{% endif %}] -pyflakes = ["+*"] -pycodestyle = ["+*" , "-D100", "-D104", "-D301", "-W503", "-W504"] - -[tool.flakehell.exceptions."**/__init__.py"]{% if not lint.docstring_checks %} -"flake8*" = ["-D*"] -pylint = ["-D*"]{% endif %} -pyflakes = ["-F401"] - -# No docs in the tests. No unused imports (otherwise pytest fixtures raise errors). -[tool.flakehell.exceptions."**/tests/*"] -pycodestyle = ["-D*"] -"flake8*" = ["-D*"] -pylint = ["-D*"] -pyflakes = ["-F401", "-F811"] - -[tool.pylint.master] -ignore = 'tests' -load-plugins =' pylint.extensions.docparams' - -[tool.pylint.messages_control] -disable = 'all,' -enable = """, - missing-param-doc, - differing-param-doc, - differing-type-doc, - missing-return-doc, - """ -{% endif %} -{{ lint.pyproject_extra }} -{% endif %} \ No newline at end of file diff --git a/src/mloq/cli.py b/src/mloq/cli.py index 849e9fe8..5c7e7c34 100644 --- a/src/mloq/cli.py +++ b/src/mloq/cli.py @@ -1,104 +1,34 @@ -"""Command line interface for mloq.""" -import os -from pathlib import Path -from typing import Callable, Optional +"""Module that contains the command line app. -import click - -from mloq.runner import run_command -from mloq.version import __version__ - - -overwrite_opt = click.option( - "--overwrite/--no-overwrite", - "-o/ ", - default=False, - show_default=True, - help="Value indicating whether to overwrite existing files.", -) -config_file_opt = click.option( - "--filename", - "-f", - "config_file", - default=None, - show_default=True, - help="Name of the repository config file", - type=click.Path(exists=True, file_okay=True, dir_okay=True, resolve_path=True), -) -only_config_opt = click.option( - "--only-config/--everything", - "-c/ ", - default=False, - show_default=True, - help="Value indicating whether to not generate all the files except mloq.yaml.", -) -output_directory_arg = click.argument( - "output_directory", - type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), -) +Why does this file exist, and why not put this in __main__? -interactive_opt = click.option( - "--interactive/--no-interactive", - "-i/ ", - default=False, - show_default=True, - help="If True the configuration values will be defined interactively on the command line.", -) + You might be tempted to import things from __main__ later, but that will cause + problems: the code will get executed twice: -hydra_args = click.argument("hydra_args", nargs=-1, type=click.UNPROCESSED) + - When you run `python -mmloq` python will execute + ``__main__.py`` as a script. That means there will not be any + ``mloq.__main__`` in ``sys.modules``. + - When you import __main__ it will get executed again (as a module) because + there"s no ``mloq.__main__`` in ``sys.modules``. + Also see (1) from http://click.pocoo.org/5/setuptools/#setuptools-integration +""" -def mloq_click_command(func): - """Wrap a command function to interface with click.""" - func = hydra_args(func) - func = only_config_opt(func) - func = interactive_opt(func) - func = overwrite_opt(func) - func = output_directory_arg(func) - func = config_file_opt(func) - func = click.command(context_settings=dict(ignore_unknown_options=True))(func) - return func - - -class MloqCLI(click.MultiCommand): - """Load the commands available at runtime from the files present in the command module.""" - - command_folder = Path(__file__).parent / "commands" +import click - def list_commands(self, ctx): - """List the names of the mloq commands available.""" - rv = [] - for filename in os.listdir(self.command_folder): - if filename.endswith(".py") and filename != "__init__.py": - rv.append(filename[:-3]) - rv.sort() - return rv +from .core import compute - def get_command(self, ctx, name) -> Callable: - """Create the command callable corresponding to the provided command name.""" - ns = {} - fn = os.path.join(self.command_folder, name + ".py") - with open(fn) as f: - code = compile(f.read(), fn, "exec") - eval(code, ns, ns) - command_class = ns[f"{name.capitalize()}CMD"] - # TODO: handle exit codes if needed - return run_command(command_class) +@click.command() +@click.argument("names", nargs=-1) +def run(names): + """Print the result of the computation. -@click.command(cls=MloqCLI) -def cli(): # noqa: D103 - pass + Args: + names (list): List of arguments. + Returns: + int: A return code. -def welcome_message(extra: bool = False, string: Optional[str] = None): - """Welcome message to be displayed during interactive setup.""" - click.echo(f"Welcome to the MLOQ {__version__} interactive setup utility.") - if extra: - click.echo(f"{string}") - click.echo() - click.echo( - "Please enter values for the following settings (just press Enter " - "to accept a default value, if one is given in brackets).", - ) - click.echo() + """ + click.echo(compute(names)) diff --git a/src/mloq/command.py b/src/mloq/command.py deleted file mode 100644 index 0bfa0832..00000000 --- a/src/mloq/command.py +++ /dev/null @@ -1,179 +0,0 @@ -"""This module defines the base Command class used for defining mloq commands.""" -from pathlib import Path -from typing import Tuple - -from omegaconf import DictConfig - -from mloq.config.configuration import as_resolved_dict -from mloq.config.prompt import Promptable -from mloq.writer import CMDRecord - - -class CommandMixin: - """Class containing the interface for defining an MLOQ Command.""" - - files: tuple = tuple() - cmd_name = "command" - - def __init__(self, record: CMDRecord, interactive: bool = False, *args, **kwargs): - """ - Instantiate the Command class. - - Args: - record: CMDRecord instance. Keeps a record of the user's - configuration. It registers the files and directories - that will be generated by mloq. - interactive: Boolean value. If True, configuration values are - introduced interactively. Otherwise, the configuration is - parsed from a user-generated configuration file. - """ - self._record = record - self.interactive = interactive - super().__init__(*args, **kwargs) - - @property - def record(self) -> CMDRecord: - """Return a CMDRecord that keeps track of the files and directories the command creates.""" - return self._record - - @property - def directories(self) -> Tuple[Path]: - """ - Tuple containing Paths objects representing the directories the Command creates. - - Override this property if your command creates any directories. - - Returns: - Tuple of :class:`Path` objects representing the path to the directories that the - :class:`Command` will create. - """ - return tuple() - - def parse_config(self) -> DictConfig: - """ - Update the configuration dictionary from the data entered by the user. - - Given the basic configuration skeleton (contained in mloq.yaml), \ - this method updates the values of those parameters (included in \ - CONFIG object) that are related to the selected command. Incoming \ - values are introduced either interactively or via a custom user's \ - mloq.yaml file. - - Returns: - It returns an updated version of the 'config' attribute of \ - the 'record' instance. - """ - self.record.update_config(DictConfig({self.cmd_name: self.config})) - return self.record.config - - def interactive_config(self) -> DictConfig: - """Pass user's configuration interactively.""" - raise NotImplementedError - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - if len(self.files) > 0: - raise NotImplementedError - - def record_directories(self) -> None: - """Register the directories that will be generated by mloq.""" - for directory in self.directories: - self._record.register_directory(directory) - - def configure(self) -> None: - """ - Save the updated version of the 'config' attribute. - - After parsing the new configuration values introduced by the user, - this method registers and saves this updated configuration within - the '_config' attribute of the 'record' instance. - """ - if self.interactive: - self.interactive_config() - # config = self.interactive_config() - else: - self.parse_config() - # config = self.parse_config() - # self.record.update_config(config) - - def run_side_effects(self) -> None: - """Apply additional configuration methods.""" - pass - - def run(self) -> CMDRecord: - """ - Record the files and directories generated by mloq according to the user's configuration. - - This method updates the configuration dictionary with the values - introduced by the user. Once the parameters have been revised, - the files and directories that will be generated by mloq are - registered within the 'record' instance. - - Returns: - It returns an updated version of the CMDRecord instance, where - the files and directories that will be generated by mloq - are recorded within the 'record' instance. - """ - self.configure() - self.record_directories() - self.record_files() - self.run_side_effects() - return self.record - - -class Command(CommandMixin, Promptable): - """ - Define blueprints for generating custom mloq commands. - - Base class used for defining new mloq commands. It establishes the - fundamental methods for defining and updating the configuration values - used to create the necessary files for the user's project, while - registering the latter for later use. - - This class is initialized from a CMDRecord instance, object where the - user's configuration, as well as the files and directories that will - be generated, are stored. - - class Attributes: - name: Name of the command. - files: Tuple containing the templates used for creating the \ - necessary files of your project. - CONFIG: NamedTuple containing the keys and values of your \ - configuration RELATIVE to the command. - """ - - def __init__(self, record: CMDRecord, interactive: bool = False, **kwargs): - """ - Instantiate the Command class. - - Args: - record: CMDRecord instance. Keeps a record of the user's - configuration. It registers the files and directories - that will be generated by mloq. - interactive: Boolean value. If True, configuration values are - introduced interactively. Otherwise, the configuration is - parsed from a user-generated configuration file. - cfg_node: key of the DictConfig in record that contains the configuration - of the current command. - """ - # self._record = record - # self.interactive = interactive - # TODO: allow interpolations in config that refer to the global record config - # cmd_conf = self._config_from_record() - # cfg_node = self.cmd_name if cfg_node is None else cfg_node - super(Command, self).__init__( - record=record, - interactive=interactive, - config=record.config, - cfg_node=self.cmd_name, - **kwargs, - ) - - def interactive_config(self) -> DictConfig: - """Pass user's configuration interactively.""" - prompt_conf = DictConfig({self.cmd_name: self.prompt.prompt_all()}) - self.record.update_config(prompt_conf) - return self.record.config - - def _config_from_record(self) -> DictConfig: - return DictConfig(as_resolved_dict(self.record.config))[self.cmd_name] diff --git a/src/mloq/commands/__init__.py b/src/mloq/commands/__init__.py deleted file mode 100644 index 8c1aa295..00000000 --- a/src/mloq/commands/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Contains all the defined mloq Commands.""" -from mloq.commands.ci import CiCMD -from mloq.commands.docker import DockerCMD -from mloq.commands.docs import DocsCMD -from mloq.commands.globals import GlobalsCMD -from mloq.commands.license import LicenseCMD -from mloq.commands.lint import LintCMD -from mloq.commands.package import PackageCMD -from mloq.commands.project import ProjectCMD -from mloq.commands.requirements import RequirementsCMD -from mloq.commands.setup import SetupCMD diff --git a/src/mloq/commands/ci.py b/src/mloq/commands/ci.py deleted file mode 100644 index a8f1bc84..00000000 --- a/src/mloq/commands/ci.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Mloq ci command implementation.""" -from pathlib import Path -from typing import Tuple - -import click -from omegaconf import DictConfig -import param - -from mloq.command import Command -from mloq.commands.package import DEFAULT_PYTHON_VERSIONS, PYTHON_VERSIONS -from mloq.files import ASSETS_PATH, file - - -CI_ASSETS_PATH = ASSETS_PATH / "ci" -push_python_wkf = file( - "push.yml", - CI_ASSETS_PATH, - "GitHub Actions continuous integration workflow file", -) - -WORKFLOW_FILES = [push_python_wkf] - - -class CiCMD(Command): - """Implement the functionality of the ci Command.""" - - cmd_name: str = "ci" - ubuntu_version = param.String(doc="Primary Ubuntu version in GitHub Actions") - disable = param.Boolean(default=False, doc="Disable ci command?") - docker = param.Boolean(doc="Test Docker container in the CI?") - project_name = param.String("${globals.project_name}", doc="Select project name") - default_branch = param.String(doc="Default branch of the project") - docker_org = param.String(doc="Name of your docker organization") - bot_name = param.String(doc="Bot's GitHub login to push commits in CI") - bot_email = param.String(doc="Bot account email") - ci_python_version = param.String(doc="Primary Python version in GitHub Actions") - python_versions = param.ListSelector( - default=DEFAULT_PYTHON_VERSIONS, - doc="Supported python versions", - objects=PYTHON_VERSIONS, - ) - ci_extra = param.String( - doc="Additional script in GitHub Actions before running the main tests", - ) - vendor = param.String(doc="Continuous Integration Vendor") - open_source = param.Boolean(doc="Is the project Open Source?") - author = param.String(doc="Author(s) of the project") - owner = param.String("${ci.author}", doc="Github handle of the project owner") - email = param.String(doc="Owner contact email") - project_url = param.String(doc="GitHub project url") - files = tuple(WORKFLOW_FILES) - - @property - def directories(self) -> Tuple[Path]: - """Tuple containing Paths objects representing the directories created by the command.""" - return tuple([Path(".github") / "workflows"]) - - def interactive_config(self) -> DictConfig: - """Generate the configuration of the project interactively.""" - click.echo("Provide the values to set up continuous integration.") - return super(CiCMD, self).interactive_config() - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - self.record.register_file(file=push_python_wkf, path=Path(".github") / "workflows") diff --git a/src/mloq/commands/docker.py b/src/mloq/commands/docker.py deleted file mode 100644 index 40ee6def..00000000 --- a/src/mloq/commands/docker.py +++ /dev/null @@ -1,117 +0,0 @@ -"""Mloq docker command implementation.""" -from pathlib import Path -from typing import Optional - -import click -from omegaconf import DictConfig, MISSING, OmegaConf - -from mloq.command import Command -from mloq.commands.requirements import ( # REQUIREMENT_CHOICES, - pytorch_req, - RequirementsCMD, - tensorflow_req, -) -from mloq.config.param_patch import param -from mloq.files import ASSETS_PATH, file - - -DOCKER_ASSETS_PATH = ASSETS_PATH / "docker" -dockerfile = file("Dockerfile", DOCKER_ASSETS_PATH, description="Docker container for the project") -makefile_docker = file( - "Makefile.docker", - DOCKER_ASSETS_PATH, - description="Makefile for the Docker container setup", - is_static=True, -) -DOCKER_FILES = [dockerfile, makefile_docker] - - -class DockerCMD(Command): - """Implement the functionality of the docker Command.""" - - cmd_name = "docker" - files = tuple(DOCKER_FILES) - disable = param.Boolean(default=None, doc="Disable docker command?") - cuda = param.Boolean(None, doc="Install CUDA?") - cuda_image_type = param.String(MISSING, doc="Type of cuda docker container") - cuda_version = param.String("11.2", doc="CUDA version installed in the container") - ubuntu_version = param.String("20.04", doc="Ubuntu version of the base image") - project_name = param.String("${globals.project_name}", doc="Select project name") - docker_org = param.String("${globals.owner}", doc="Name of your Docker organization") - python_version = param.String("3.8", doc="Python version installed in the container") - base_image = param.String(MISSING, doc="Base Docker image used to build the container") - test = param.Boolean(True, doc="Install requirements-test.txt?") - lint = param.Boolean(True, doc="Install requirements-lint.txt?") - jupyter = param.Boolean(True, doc="Install a jupyter notebook server?") - jupyter_password = param.String( - "${docker.project_name}", - doc="password for the Jupyter notebook server", - ) - requirements = param.List(default=["none"], doc="Project requirements") - extra = param.String("", doc="Extra code to add to Dockerfile") - makefile = param.Boolean(True, doc="Add docker commands to makefile") - # requirements = param.ListSelector( - # default="none", doc="Project requirements", objects=REQUIREMENT_CHOICES, - # ) - - @staticmethod - def require_cuda_from_requirements(project_config: Optional[DictConfig] = None) -> bool: - """Return True if any of the project dependencies require CUDA.""" - project_config = {} if project_config is None else project_config - if "requirements" not in project_config: - return False - options = project_config.get("requirements", []) - if RequirementsCMD.requirements_is_empty(options): - return False - elif isinstance(options, str): - options = [options] - tf_alias = RequirementsCMD.REQUIREMENTS_ALIASES[tensorflow_req] - torch_alias = RequirementsCMD.REQUIREMENTS_ALIASES[pytorch_req] - for option in options: - if option in tf_alias or option in torch_alias: - return True - return False - - def requires_cuda(self) -> bool: - """Return True if the Docker container requires CUDA.""" - if not OmegaConf.is_missing(self.config, "cuda") and self.cuda is not None: - return self.config.cuda - try: - _ = self.config.requirements - except Exception: - self.config.requirements = [] - return self.require_cuda_from_requirements(self.config) - - def get_base_image(self): - """Return the name of the base image for the project Docker container.""" - # Check value is not missing and it is not None due to a bad interpolation resolution - if ( - not OmegaConf.is_missing(self.config, "base_image") - and self.config.base_image is not None - ): - return self.config.base_image - elif self.config.cuda: - cuda_image = ( - f"nvidia/cuda:{self.config.cuda_version}-{self.config.cuda_image_type}" - f"-ubuntu{self.config.ubuntu_version}" - ) - return cuda_image - return f"ubuntu:{self.config.ubuntu_version}" - - def parse_config(self) -> DictConfig: - """Update the configuration dictionary from the data entered by the user.""" - super(DockerCMD, self).parse_config() - if self.cuda is None: - self.cuda = self.requires_cuda() - self.base_image = self.get_base_image() - return super(DockerCMD, self).parse_config() - - def interactive_config(self) -> DictConfig: - """Generate the configuration of the project interactively.""" - click.echo("Provide the values to generate the project Docker container.") - return self.parse_config() - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - self.record.register_file(file=dockerfile, path=Path()) - self.record.register_file(file=makefile_docker, path=Path()) diff --git a/src/mloq/commands/docs.py b/src/mloq/commands/docs.py deleted file mode 100644 index 23c6fde9..00000000 --- a/src/mloq/commands/docs.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Mloq docs command implementation.""" -from pathlib import Path -from typing import Tuple - -import click -from omegaconf import DictConfig -import param - -from mloq.command import Command -from mloq.files import ASSETS_PATH, file - - -DOCS_ASSETS_PATH = ASSETS_PATH / "docs" -# Documentation files -makefile_docs = file( - "makefile_docs.txt", - DOCS_ASSETS_PATH, - "common make commands for building the documentation", - dst="Makefile", - is_static=True, -) -make_bat_docs = file( - "make_bat.txt", - DOCS_ASSETS_PATH, - "common make commands for building the documentation", - dst="make.bat", - is_static=True, -) -docs_req = file( - "requirements-docs.txt", - DOCS_ASSETS_PATH, - "list of exact versions of the packages needed to build your documentation", - is_static=True, -) -conf_py = file( - "conf.txt", - DOCS_ASSETS_PATH, - "configuration file for sphinx and doc plugins", - dst="conf.py", -) -index_md = file( - "index.md", - DOCS_ASSETS_PATH, - "configuration file for sphinx and doc plugins", -) -deploy_docs = file( - "deploy-docs.yml", - DOCS_ASSETS_PATH, - "workflow for deploying the documentation to GitHub pages", -) - -DOCS_FILES = [conf_py, index_md, makefile_docs, make_bat_docs, docs_req, deploy_docs] - - -class DocsCMD(Command): - """Implement the functionality of the docs Command.""" - - cmd_name = "docs" - disable = param.Boolean(False, doc="Disable docs command?") - project_name = param.String(doc="Select project name") - description = param.String(doc="Short description of the project") - author = param.String("${globals.author}", doc="Author(s) of the project") - copyright_year = param.Integer(doc="Year when the project started") - copyright_holder = param.String("${docs.author}", doc="Copyright holder") - deploy_docs = param.Boolean(True, doc="Deploy docs to GitHub Pages?") - default_branch = param.String("${globals.default_branch}", doc="Branch used to build the docs") - project_url = param.String("${globals.project_url}", doc="GitHub project url") - files = tuple(DOCS_FILES) - - @property - def directories(self) -> Tuple[Path]: - """Tuple containing Paths objects representing the directories created by the command.""" - docs_folder = Path("docs") / "source" / "markdown" - paths = [docs_folder] - if self.deploy_docs: - workflow_path = Path(".github") / "workflows" - paths.append(workflow_path) - return tuple(paths) - - def interactive_config(self) -> DictConfig: - """Generate the configuration of the project interactively.""" - click.echo("Provide the values to generate the project documentation.") - return super(DocsCMD, self).interactive_config() - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - source_files = {"conf.py", "index.md"} - docs_ASSETS_path = Path("docs") - for _file in self.files: - if _file == deploy_docs: - if not self.deploy_docs: - continue - path = Path(".github") / "workflows" - else: - path = ( - (docs_ASSETS_path / "source") - if str(_file.dst) in source_files - else docs_ASSETS_path - ) - self.record.register_file(file=_file, path=path) diff --git a/src/mloq/commands/globals.py b/src/mloq/commands/globals.py deleted file mode 100644 index b015b311..00000000 --- a/src/mloq/commands/globals.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Mloq globals command implementation.""" -import click -from omegaconf import DictConfig, OmegaConf -import param - -from mloq.command import Command - - -class GlobalsCMD(Command): - """Implement the functionality of the globals Command.""" - - cmd_name = "globals" - project_name = param.String(doc="Select project name") - description = param.String(doc="Short description of the project") - author = param.String(doc="Author(s) of the project") - owner = param.String("${globals.author}", doc="Github handle of the project owner") - email = param.String(doc="Owner contact email") - open_source = param.Boolean(doc="Is the project Open Source?") - project_url = param.String("???", doc="GitHub project url") - default_branch = param.String("main", doc="Default branch of the project") - license = param.String("MIT", doc="Project license type") - use_poetry = param.Boolean(True, doc="Use poetry to manage dependencies?") - main_python_version = param.String("3.8", doc="Python version for CI, Poetry and Docker") - - def interactive_config(self) -> DictConfig: - """Generate the configuration of the project interactively.""" - click.echo("The following values will occur in several places in the generated files.") - return super(GlobalsCMD, self).interactive_config() - - def parse_config(self) -> DictConfig: - """Generate the configuration of the project via a configuration file.""" - default_url = f"https://github.com/{self.owner}/{self.project_name.replace(' ', '-')}" - self.project_url = OmegaConf.select(self.config, "project_url", default=default_url) - self.conf.sync() - return super(GlobalsCMD, self).parse_config() diff --git a/src/mloq/commands/license.py b/src/mloq/commands/license.py deleted file mode 100644 index fd86df56..00000000 --- a/src/mloq/commands/license.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Mloq license command implementation.""" -from pathlib import Path - -import click -from omegaconf import DictConfig - -from mloq.command import Command -from mloq.config.param_patch import param -from mloq.files import ASSETS_PATH, file - - -TEMPLATES_PATH = ASSETS_PATH / "license" -dco = file( - "DCO.md", - TEMPLATES_PATH, - "Developer Certificate of Origin - needed in open source projects to certify that " - "the incoming contributions are legitimate", - is_static=True, -) -mit_license = file("MIT_LICENSE", TEMPLATES_PATH, "license of the project", dst="LICENSE") -apache_license = file("APACHE_LICENSE", TEMPLATES_PATH, "license of the project", dst="LICENSE") -gpl_license = file( - "GPL_LICENSE", - TEMPLATES_PATH, - "license of the project", - dst="LICENSE", - is_static=True, -) -LICENSES = { - "MIT": mit_license, - "Apache-2.0": apache_license, - "GPL-3.0": gpl_license, -} - -LICENSE_FILES = [file for file in LICENSES.values()] + [dco] - - -class LicenseCMD(Command): - """Implement the functionality of the license Command.""" - - cmd_name = "license" - files = tuple(LICENSE_FILES) - LICENSES = LICENSES - disable = param.Boolean(default=None, doc="Disable license command?") - license = param.String("MIT", doc="Project license type") - copyright_year = param.Integer("${current_year:}", doc="Year when the project started") - copyright_holder = param.String("${globals.owner}", doc="Copyright holder") - project_name = param.String(doc="Select project name") - project_url = param.String("${globals.project_url}", doc="GitHub project url") - email = param.String("${globals.email}", doc="Owner contact email") - - def interactive_config(self) -> DictConfig: - """Generate the configuration of the project interactively.""" - click.echo("Provide the values to generate the project license files.") - return self.parse_config() - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - conf = self.record.config.license - self.record.register_file(file=dco, path=Path()) - if conf.license != "proprietary": - license_file = self.LICENSES[conf.license] - self.record.register_file(file=license_file, path=Path()) diff --git a/src/mloq/commands/lint.py b/src/mloq/commands/lint.py deleted file mode 100644 index 2e238ae6..00000000 --- a/src/mloq/commands/lint.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Mloq lint command implementation.""" -from pathlib import Path - -import click -from omegaconf import DictConfig - -from mloq.command import Command -from mloq.config.param_patch import param -from mloq.files import ASSETS_PATH, file, pyproject_toml - - -lint_req = file( - "requirements-lint.txt", - ASSETS_PATH / "lint", - "list of exact versions of the packages used to check your code style", - is_static=True, -) -LINT_FILES = [pyproject_toml, lint_req] - - -class LintCMD(Command): - """Implement the functionality of the lint Command.""" - - cmd_name = "lint" - files = tuple(LINT_FILES) - disable = param.Boolean(default=None, doc="Disable lint command?") - black = param.Boolean(True, doc="Use black for code formatting?") - isort = param.Boolean(True, doc="Use isort for sorting imports automatically?") - linters = param.Boolean(True, doc="Configure code linters?") - docstring_checks = param.Boolean(True, doc="Apply docstring checks?") - pyproject_extra = param.String("", doc="Additional pyproject.toml configuration") - project_name = param.String("${globals.project_name}", doc="Select project name") - makefile = param.Boolean(True, doc="Add check and style commands to makefile") - poetry_requirements = param.Boolean(True, doc="Add check and style commands to makefile") - ignore_files = param.ListSelector( - default=[], - doc="Ignore the following files", - objects=[f.dst for f in LINT_FILES], - ) - - def interactive_config(self) -> DictConfig: - """Generate the configuration of the project interactively.""" - click.echo("Provide the values to generate the packaging files.") - return self.parse_config() - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - for _file in self.files: - if _file.dst not in self.record.config.lint.ignore_files: - self.record.register_file(file=_file, path=Path()) diff --git a/src/mloq/commands/package.py b/src/mloq/commands/package.py deleted file mode 100644 index 4d5f8d41..00000000 --- a/src/mloq/commands/package.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Mloq package command implementation.""" -from pathlib import Path - -import click -from omegaconf import DictConfig, MISSING, OmegaConf - -from mloq.command import Command -from mloq.config.param_patch import param -from mloq.files import ASSETS_PATH, file, pyproject_toml - - -PACKAGE_ASSETS_PATH = ASSETS_PATH / "package" -PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] -DEFAULT_PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10"] -setup_py = file( - "setup.txt", - PACKAGE_ASSETS_PATH, - "Python package installation metadata", - dst="setup.py", -) -PACKAGE_FILES = [pyproject_toml, setup_py] - - -class PackageCMD(Command): - """Implement the functionality of the package Command.""" - - cmd_name = "package" - files = tuple(PACKAGE_FILES) - LICENSE_CLASSIFIERS = { - "MIT": "License :: OSI Approved :: MIT License", - "Apache-2.0": "License :: OSI Approved :: Apache Software License", - "GPL-3.0": "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "proprietary": "License :: Other/Proprietary License", - } - disable = param.Boolean(default=None, doc="Disable package command?") - pyproject_extra = param.String("", doc="Additional pyproject.toml configuration") - project_name = param.String("${globals.project_name}", doc="Select project name") - license = param.String("MIT", doc="Project license type") - license_classifier = param.String(MISSING, doc="License classifier in setup.py") - description = param.String("${globals.description}", doc="Short description of the project") - default_branch = param.String(doc="Default branch of the project") - project_url = param.String("${globals.project_url}", doc="GitHub project url") - owner = param.String("${globals.owner}", doc="Github handle of the project owner") - author = param.String(doc="Author(s) of the project") - email = param.String(doc="Owner contact email") - main_python_version = param.String( - "${globals.main_python_version}", - doc="Python version for CI, Poetry and Docker", - ) - python_versions = param.ListSelector( - default=DEFAULT_PYTHON_VERSIONS, - doc="Supported python versions", - objects=PYTHON_VERSIONS, - ) - use_poetry = param.Boolean( - "${globals.use_poetry}", - doc="Use poetry to manage dependencies", - ) - - def parse_config(self) -> DictConfig: - """Update the configuration DictConfig with the Command parameters.""" - conf = super(PackageCMD, self).parse_config() - if OmegaConf.is_missing(conf.package, "license_classifier"): - conf.package.license_classifier = self.LICENSE_CLASSIFIERS.get( - conf.package.get("license", "proprietary"), - "", - ) - return conf - - def interactive_config(self) -> DictConfig: - """Generate the configuration of the project interactively.""" - click.echo("Provide the values to generate the packaging files.") - return self.parse_config() - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - for _file in self.files: - self.record.register_file(file=_file, path=Path()) diff --git a/src/mloq/commands/project.py b/src/mloq/commands/project.py deleted file mode 100644 index 9eed7dc5..00000000 --- a/src/mloq/commands/project.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Mloq project command implementation.""" -from pathlib import Path -from typing import Tuple - -from mloq.command import Command -from mloq.config.param_patch import param -from mloq.files import ASSETS_PATH, file, makefile - - -PROJECT_ASSETS_PATH = ASSETS_PATH / "project" -readme = file("README.md", PROJECT_ASSETS_PATH, "README") -gitignore = file( - ".gitignore", - PROJECT_ASSETS_PATH, - "list of files and directories ignored by Git operations", - is_static=True, -) -pre_commit_hook = file( - ".pre-commit-config.yaml", - PROJECT_ASSETS_PATH, - "Git pre-commit hooks configuration", - is_static=True, -) -init = file( - "init.txt", - PROJECT_ASSETS_PATH, - "Python package header", - dst="__init__.py", - is_static=True, -) -main = file( - "main.txt", - PROJECT_ASSETS_PATH, - "Python package executable entry point", - dst="__main__.py", - is_static=True, -) -test_main = file( - "test_main.txt", - PROJECT_ASSETS_PATH, - "Unit test of the python package executable entry point", - dst="test_main.py", - is_static=False, -) -test_req = file( - "requirements-test.txt", - PROJECT_ASSETS_PATH, - "list of exact versions of the packages needed to run your test suite", - is_static=True, -) -version = file( - "version.txt", - PROJECT_ASSETS_PATH, - "defines the version of the package that is incremented on each push", - dst="version.py", - is_static=True, -) -code_of_conduct = file( - "CODE_OF_CONDUCT.md", - PROJECT_ASSETS_PATH, - "behavioral rules and norms in open source projects", -) -codecov = file( - ".codecov.yml", - PROJECT_ASSETS_PATH, - "configuration of CodeCov service to track the code coverage", -) -contributing = file( - "CONTRIBUTING.md", - PROJECT_ASSETS_PATH, - "technical manual on how to contrib to the open source project", -) - -PROJECT_FILES = [ - codecov, - gitignore, - readme, - makefile, - init, - main, - test_main, - version, - test_req, - pre_commit_hook, - contributing, - code_of_conduct, -] - -# TODO: select which files are created in config -# package folders: __init__, __main__, version -# contributing -# codecov -# gitignore -# makefile -# pre commit - - -class ProjectCMD(Command): - """Implement the functionality of the project Command.""" - - cmd_name = "project" - files = tuple(PROJECT_FILES) - disable = param.Boolean(default=False, doc="Disable project command?") - project_name = param.String("${globals.project_name}", doc="Select project name") - owner = param.String("${globals.owner}", doc="Github handle of the project owner") - description = param.String("${globals.description}", doc="Short description of the project") - project_url = param.String("${globals.project_url}", doc="GitHub project url") - license = param.String("MIT", doc="Project license type") - tests = param.Boolean(True, doc="Add support for pytest") - - @property - def directories(self) -> Tuple[Path]: - """Tuple containing Paths objects representing the directories created by the command.""" - project_folder = self.record.config.project.project_name.replace(" ", "_") - - return tuple([Path("src") / project_folder, Path("tests")]) - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - self.record.register_file(file=readme, path=Path()) - self.record.register_file(file=makefile, path=Path()) - project_folder = Path("src") / self.record.config.project.project_name.replace(" ", "_") - description = "Python package header for the project module" - self.record.register_file(file=init, path=project_folder, description=description) - self.record.register_file(file=main, path=project_folder) - self.record.register_file(file=version, path=project_folder) - description = "Python package header for the test module" - self.record.register_file(file=init, path=Path("tests"), description=description) - self.record.register_file(file=test_main, path=Path("tests")) - root_files = [ - readme, - makefile, - test_req, - pre_commit_hook, - codecov, - gitignore, - code_of_conduct, - contributing, - ] - for _file in root_files: - self.record.register_file(file=_file, path=Path()) diff --git a/src/mloq/commands/requirements.py b/src/mloq/commands/requirements.py deleted file mode 100644 index 749bd22f..00000000 --- a/src/mloq/commands/requirements.py +++ /dev/null @@ -1,212 +0,0 @@ -"""Mloq requirements command implementation.""" -from pathlib import Path -import tempfile -from typing import Iterable, List, Union - -import click -from omegaconf import DictConfig - -from mloq.command import Command -from mloq.config.param_patch import param -from mloq.files import ASSETS_PATH, File, file -from mloq.record import CMDRecord - - -# Requirements files -REQUIREMENTS_PATH = ASSETS_PATH / "requirements" -requirements = file( - "requirements.txt", - REQUIREMENTS_PATH, - "list of exact versions of the packages on which your project depends", - is_static=True, -) -data_science_req = file( - "data-science.txt", - REQUIREMENTS_PATH, - "list of commonly used data science libraries", -) -data_viz_req = file( - "data-visualization.txt", - REQUIREMENTS_PATH, - "list of commonly used visualization libraries", - is_static=True, -) -pytorch_req = file( - "pytorch.txt", - REQUIREMENTS_PATH, - "Pytorch deep learning libraries", - is_static=True, -) -tensorflow_req = file( - "tensorflow.txt", - REQUIREMENTS_PATH, - "Tensorflow deep learning libraries", - is_static=True, -) -lint_req = file( - "requirements-lint.txt", - REQUIREMENTS_PATH, - "list of exact versions of the packages used to check your code style", - is_static=True, -) -test_req = file( - "requirements-test.txt", - REQUIREMENTS_PATH, - "list of exact versions of the packages needed to run your test suite", - is_static=True, -) -dogfood_req = file( - "dogfood.txt", - REQUIREMENTS_PATH, - "list of mock requirements for testing purposes", - is_static=True, -) -docs_req = file( - "requirements-docs.txt", - REQUIREMENTS_PATH, - "list of exact versions of the packages needed to build your documentation", - is_static=True, -) -REQUIREMENTS_FILES = [pytorch_req, data_science_req, data_viz_req, tensorflow_req] - -REQUIREMENT_CHOICES = [ - "data-science", - "data-viz", - "torch", - "tensorflow", - "none", - "dogfood", - "None", -] - - -class RequirementsCMD(Command): - """Implement the functionality of the requirements Command.""" - - cmd_name = "requirements" - files = tuple(REQUIREMENTS_FILES) - disable = param.Boolean(default=None, doc="Disable requirements command?") - requirements = param.ListSelector( - default=["none"], - doc="Project requirements", - objects=REQUIREMENT_CHOICES, - ) - REQUIREMENTS_ALIASES = { - data_science_req: ["data-science", "datascience", "ds"], - pytorch_req: ["pytorch", "torch"], - tensorflow_req: ["tensorflow", "tf"], - data_viz_req: ["data-visualization", "data-viz", "data-vis", "dataviz", "datavis"], - } - - def __init__(self, record: CMDRecord, interactive: bool = False): - """ - Initialize a RequirementsCMD class. - - Args: - record: CMDRecord where the command data will be written. - interactive: If True, parse the command configuration in interactive mode. - """ - super(RequirementsCMD, self).__init__(record=record, interactive=interactive) - self._temp_dir = tempfile.TemporaryDirectory() - # File objects work referencing files present in the system. We create a requirements.txt - # temporary file to be consistent with that behavior. - reqs_src = Path(self._temp_dir.name) / "requirements.txt" - self._reqs_file = File( - name=requirements.name, - src=reqs_src, - dst=requirements.dst, - is_static=requirements.is_static, - description=requirements.description, - ) - self.files = tuple(list(self.files) + [self._reqs_file]) - - def __del__(self) -> None: - """Remove the temporary directory when the instance is deleted.""" - self._temp_dir.cleanup() - - @classmethod - def get_aliased_requirements_file(cls, option: str) -> File: - """Get requirement file from aliased name.""" - for _file, valid_alias in cls.REQUIREMENTS_ALIASES.items(): - if option in valid_alias: - return _file - if option == "dogfood": - return dogfood_req - raise KeyError( - f"{option} is not a valid name. Valid aliases are {cls.REQUIREMENTS_ALIASES}", - ) - - @classmethod - def read_requirements_file(cls, option: str) -> str: - """Return the content of the target requirements file form an aliased name.""" - req_file = cls.get_aliased_requirements_file(option) - with open(req_file.src, "r") as f: - return f.read() - - @classmethod - def compose_requirements(cls, options: Iterable[str]) -> str: - """ - Return the content requirements.txt file with pinned dependencies. - - The returned string contains the combined dependencies\ - for the different options sorted alphabetically. - - Args: - options: Iterable containing the aliased names of the target \ - dependencies for the project. - - Returns: - str containing the pinned versions of all the selected requirements. - """ - requirements_text = "" - for i, opt in enumerate(options): - pref = "\n" if i > 0 else "" # Ensure one requirement per line - requirements_text += pref + cls.read_requirements_file(opt) - # Sort requirements alphabetically - requirements_text = "\n".join(sorted(requirements_text.split("\n"))).lstrip("\n") - return requirements_text - - '''def ___parse_config(self) -> DictConfig: - """Update the configuration DictConfig with the Command parameters.""" - value = self.CONFIG.requirements(self.record.config, self.interactive) - self.record.config.requirements = value - return self.record.config''' - - def interactive_config(self) -> DictConfig: - """Generate the configuration of the project interactively.""" - click.echo("Please specify the requirements of the project as a comma separated list.") - click.echo("Available values:") - click.echo( - " data-science: Common data science libraries such as numpy, pandas, sklearn...", - ) - click.echo( - ( - " data-viz: Visualization libraries such as holoviews, ", - "bokeh, plotly, matplotlib...", - ), - ) - click.echo(" pytorch: Latest version of pytorch, torchvision and pytorch_lightning") - click.echo(" tensorflow: ") # , data-viz, torch, tensorflow}") - return self.parse_config() - - @staticmethod - def requirements_is_empty(options: Union[List[str], str]) -> bool: - """Return True if no requirements are specified for the project.""" - if not options: - return True - if isinstance(options, str): - options = [options] - if None in options or "None" in options or "none" in options: - return True - return False - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - reqs_value = self.record.config.requirements.requirements - if self.requirements_is_empty(reqs_value): - return - reqs_content = self.compose_requirements(reqs_value) - with open(self._reqs_file.src, "w") as f: - f.write(reqs_content) - - self.record.register_file(file=self._reqs_file, path=Path()) diff --git a/src/mloq/commands/setup.py b/src/mloq/commands/setup.py deleted file mode 100644 index 613e6815..00000000 --- a/src/mloq/commands/setup.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Mloq setup command implementation.""" -from pathlib import Path -from typing import List, Tuple - -import click -from omegaconf import DictConfig - -from mloq.command import Command, CommandMixin -from mloq.record import CMDRecord - - -def _sub_commands(): - from mloq.commands import ( - CiCMD, - DockerCMD, - DocsCMD, - GlobalsCMD, - LicenseCMD, - LintCMD, - PackageCMD, - ProjectCMD, - RequirementsCMD, - ) - - return ( - GlobalsCMD, - ProjectCMD, - PackageCMD, - LicenseCMD, - LintCMD, - CiCMD, - DocsCMD, - DockerCMD, - RequirementsCMD, - ) - - -SUB_COMMANDS = _sub_commands() - - -class SetupCMD(CommandMixin): - """Implement the functionality of the setup Command.""" - - cmd_name = "setup" - files = tuple([file for cmd in SUB_COMMANDS for file in cmd.files]) - SUB_COMMAND_CLASSES = SUB_COMMANDS - - def __init__(self, record: CMDRecord, interactive: bool = False): - """ - Initialize a SetupCMD class. - - Args: - record: CMDRecord where the command data will be written. - interactive: If True, parse the command configuration in interactive mode. - """ - super(SetupCMD, self).__init__(record=record, interactive=interactive) - self._sub_commands = [ - cmd(record=self.record, interactive=interactive) for cmd in self.SUB_COMMAND_CLASSES - ] - self.files = tuple([file for cmd in self._sub_commands for file in cmd.files]) - - @property - def config(self) -> DictConfig: - """List of all the commands that will be executed when running mloq setup.""" - return self.record.config - - @property - def sub_commands(self) -> List[Command]: - """List of all the commands that will be executed when running mloq setup.""" - return self._sub_commands - - @property - def directories(self) -> Tuple[Path]: - """Tuple containing Paths objects representing the directories created by the command.""" - return tuple([directory for cmd in self.sub_commands for directory in cmd.directories]) - - def interactive_config(self) -> DictConfig: - """Generate the configuration of the project interactively.""" - click.echo("Provide the values to generate the project configuration.") - for cmd in self.sub_commands: - cmd.interactive_config() - return self.record.config - - def parse_config(self) -> DictConfig: - """Update the configuration DictConfig with the Command parameters.""" - for cmd in self.sub_commands: - cmd.parse_config() - return self.record.config - - def run_side_effects(self) -> None: - """Apply additional configuration methods.""" - for cmd in self.sub_commands: - cmd.run_side_effects() - - def record_files(self) -> None: - """Register the files that will be generated by mloq.""" - for cmd in self.sub_commands: - cmd.record_files() diff --git a/src/mloq/config/__init__.py b/src/mloq/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/mloq/config/configuration.py b/src/mloq/config/configuration.py deleted file mode 100644 index 709ae715..00000000 --- a/src/mloq/config/configuration.py +++ /dev/null @@ -1,544 +0,0 @@ -""" -This module defines the Configurable class and associated logic. - -The Configurable class extends the param.Parameterizable class to keep track of -the class parameters using an omegaconf.DictConfig. -""" -import copy -import dataclasses -from dataclasses import field, make_dataclass -from enum import Enum -from typing import Any, Dict, List, Optional, Tuple, Union - -import omegaconf -from omegaconf import Container, MISSING, OmegaConf -from omegaconf.errors import InterpolationToMissingValueError, MissingMandatoryValue -import param as param__ -from typing_extensions import Protocol - -from mloq.config.param_patch import param - - -# -try: - from mypy.typeshed.stdlib.dataclasses import Field - - DClassField = Field[Any] -except ModuleNotFoundError: - DClassField = Any - - -class Dataclass(Protocol): - """Type hinting to defined a dataclass as a typing Protocol.""" - - # as already noted in comments, checking for this attribute is currently - # the most reliable way to ascertain that something is a dataclass - __dataclass_fields__: Dict - - -ConfigValue = Any - -ConfigurationDict = Union[Dict[Union[str, int, Enum, float, bool], ConfigValue]] - -PythonType = Union[float, int, bool, str, list, dict, tuple] - -ParamType = Union[ - param.Number, - param.Integer, - param.Boolean, - param.String, - param.Array, - param.List, - param.Dict, - param.Tuple, -] - -DataClassDict = Dict[str, Tuple[type, DClassField]] - - -class DictConfig(param.ClassSelector): - """param.Parameter that defines a DictConfig object.""" - - def __init__( - self, - default: Optional[omegaconf.DictConfig] = None, - doc: Optional[str] = None, - instantiate: bool = True, - per_instance: bool = True, - **kwargs, - ): - """ - Initialize a DictConfig. - - Args: - default: Default value of the Parameter. Must be an instance of DictConfig. - doc: Documentation of the Parameter. - instantiate: Expect and instance of DictConfig. - per_instance: Create a new DictConfig instance every time you initialize a - Parameterized class. - **kwargs: Passed to param.ClassSelector.__init__. - """ - default = omegaconf.DictConfig({}) if default is None else default - if doc is None: - doc = "Structured omegaconf.DictConfig representing the param.Parameters information" - kwargs["class_"] = omegaconf.DictConfig - - super(DictConfig, self).__init__( - default=default, - doc=doc, - instantiate=instantiate, - per_instance=per_instance, - **kwargs, - ) - - -PARAM_TO_TYPE = { - param.Boolean: bool, - param.Integer: int, - param.Number: float, - param.String: str, - param.List: list, - param.ListSelector: list, - param.Dict: dict, - param.Tuple: tuple, - param__.Boolean: bool, - param__.Integer: int, - param__.Number: float, - param__.String: str, - param__.List: list, - param__.ListSelector: list, - param__.Dict: dict, - param__.Tuple: tuple, - DictConfig: omegaconf.DictConfig, -} - - -def param_to_dataclass_dict( - obj: Union[param.Parameterized, Any], -) -> Dict[str, Tuple[type, DClassField]]: - """ - Create a dictionary that can be used to initialize a dataclass containing the parameters \ - of the target param.Parameterized class. - - Args: - obj: Class or instance of a param.Parameterized class. - - Returns: - dict containing the fields required to define a dataclass with the obj parameters. - """ - data = {} - for k, v in obj.params().items(): - if k in ["name", "config"]: - continue - for param_type, _type in PARAM_TO_TYPE.items(): - if isinstance(v, param_type): - value = getattr(obj, k) if isinstance(obj, param.Parameterized) else v.default - data[k] = (_type, field(default=value)) - break - return data - - -def param_to_dataclass(obj: Union[param.Parameterized, Any]) -> type: - """Create a dataclass equivalent to the target param.Parameterized target.""" - name = obj.__class__.name if isinstance(obj, param.Parameterized) else obj.name - # FIXME: Assumes all keys are strings - datac = make_dataclass( - name, - [(str(k), t, v) for k, (t, v) in param_to_dataclass_dict(obj).items()], - ) - return datac - - -def param_to_omegaconf(obj: Union[param.Parameterized, Any]) -> omegaconf.DictConfig: - """Transform a param.Parameterized class into an OmegaConf structured configuration.""" - return OmegaConf.structured(param_to_dataclass(obj)) - - -def is_interpolation(s: str) -> bool: - """Return True if the provided string is an OmegaConf interpolation string.""" - if not isinstance(s, str): - return False - return "${" in s and "}" in s # TODO: use regex - - -def to_param_type(obj: param.Parameterized, config: DictConfig, key: str) -> Any: - """Transform the provided attribute of the target param.Parameterized object \ - into the appropriate type so it can be stored in a configuration file.""" - # Yaml cannot handle tuples, so we convert the value - config = copy.deepcopy(config) - OmegaConf.resolve(config) - param_obj = obj.param.params().get(key) - value = config[key] if not OmegaConf.is_missing(config, key) else param_obj.default - if isinstance(value, omegaconf.ListConfig): - value = [x for x in value] - elif isinstance(value, omegaconf.DictConfig): - value = {**value} - type_ = PARAM_TO_TYPE.get(param_obj.__class__) - if type_ and not isinstance(value, type_): - if value is None: - value = value if param_obj.allow_None else type_() - else: - value = type_(value) # if value is not None else type_() - - return value - - -def to_config( - config: Union[ - omegaconf.DictConfig, - ConfigurationDict, - Dataclass, - param.Parameterized, - None, - ], - **kwargs, -) -> omegaconf.DictConfig: - """Transform the provided object into an omegaconf.DictConfig.""" - if isinstance(config, param.Parameterized): - config = param_to_omegaconf(config) - elif dataclasses.is_dataclass(config): - config = OmegaConf.structured(config, **kwargs) - elif not isinstance(config, omegaconf.DictConfig): - config = OmegaConf.create(config, **kwargs) - elif config is None: - return omegaconf.DictConfig({}) - return config - - -def resolve_as_dict( - obj, - config: Union[omegaconf.DictConfig, ConfigurationDict, Dataclass, param.Parameterized], - **kwargs, -) -> ConfigurationDict: - """Transform the provided object into a dictionary resolving all its interpolations.""" - config: Union[Container, omegaconf.DictConfig] = to_config(config, **kwargs) - OmegaConf.resolve(config) - param_data: Dict[str, Any] = {k: to_param_type(obj, config, k) for k in config} - return param_data - - -def safe_select(cfg: DictConfig, key: str, default: Any = None) -> Any: - """ - Access safely the target value of the provided cfg DictConfig. - - Return MISSING if the value cannot be resolved or it's missing. - """ - try: - return OmegaConf.select( - cfg=cfg, - key=key, - default=default, - throw_on_resolution_failure=True, - throw_on_missing=True, - ) - except (MissingMandatoryValue, InterpolationToMissingValueError): # , InterpolationKeyError): - return MISSING - - -def as_resolved_dict(cfg: DictConfig) -> ConfigurationDict: - """Return a dictionary containing the resolved values for the provided DictConfig.""" - resolved_dict = {k: safe_select(cfg, k) for k in cfg.keys()} - return resolved_dict - - -class OmegaConfInterface: - """Common functionality to work with configurations.""" - - def __init__(self, target: "Configurable", allow_missing: bool = False): - """ - Initialize an OmegaConfInterface. - - Args: - target: Keep track of target param values using a DictConfig. - allow_missing: Allow missing values in the target configuration. - """ - self._target = target - self.allow_missing = allow_missing - - @property - def config(self) -> omegaconf.DictConfig: - """Return a DictConfig containing the target configuration.""" - return self._target.config - - @property - def interpolations(self) -> ConfigurationDict: - """Return a dictionary containing the interpolations of the target configuration.""" - cont = OmegaConf.to_container(self.config, resolve=False) - return {k: v for k, v in cont.items() if OmegaConf.is_interpolation(self.config, str(k))} - - @property - def missing(self) -> List[Union[str, int, Enum, float, bool]]: - """Return a list containing the names of the configuration that are MISSING.""" - return [k for k, v in as_resolved_dict(self.config).items() if v == MISSING] - - def _resolve_inplace(self, key: Optional[str] = None) -> None: - """Resolve and update the target attribute if it's an interpolation string.""" - if key is None: - OmegaConf.resolve(self._target.config) - return - self.config[key] = self.select(key=key) - - def resolve( - self, - key: Optional[str] = None, - inplace: bool = False, - ) -> Union[Container, ConfigValue, None]: - """ - Resolve the target attribute if it is an interpolation string. - - Args: - key: Name of the target's attribute to be resolved. - inplace: If True, update the configuration value replacing the - interpolation string with the resolved value. - - Returns: - Resolved value of the target attribute. - """ - if inplace: - return self._resolve_inplace(key) - value = as_resolved_dict(self.config) if key is None else self.select(key=key) - return value - - def is_missing(self, key: str) -> bool: - """Return True if the key target's attribute is Missing, otherwise return False.""" - return safe_select(self.config, key) == MISSING - - def is_interpolation(self, key: str) -> bool: - """Check if the key target's attribute is an interpolation string.""" - return OmegaConf.is_interpolation(self.config, key) - - def select(self, key, default=None) -> Any: - """ - Select the key target's attribute. - - Return MISSING if key corresponds to a missing value, or an - interpolation that resolves to a missing value. - """ - return safe_select(self.config, key=key, default=default) - - -class BaseConfig(OmegaConfInterface): - """Manages getters and setters to access the target's configuration values.""" - - def __init__( - self, - target: "Configurable", - config: Optional[Union[ConfigurationDict, omegaconf.DictConfig]] = None, - cfg_node: Optional[str] = None, - allow_missing: bool = False, - **kwargs, - ): - """Initialize a BaseConfig.""" - super(BaseConfig, self).__init__(target=target, allow_missing=allow_missing) - self._setup_config(config, cfg_node=cfg_node, **kwargs) - - def __getitem__(self, item: str) -> Any: - """Access the target config value.""" - return self.config[item] - - def __setitem__(self, key: str, value) -> Any: - """Set the target config value.""" - self.config[key] = value - - def to_container(self, resolve: bool = False, **kwargs) -> Container: - """Return a container containing the target's configuration.""" - try: - return OmegaConf.to_container(self.config, resolve=resolve, **kwargs) - except (MissingMandatoryValue, InterpolationToMissingValueError): - d = omegaconf.DictConfig(as_resolved_dict(self.config)) - return OmegaConf.to_container(d, resolve=resolve, **kwargs) - - @staticmethod - def _resolve_node( - kwargs: dict, - config: Optional[omegaconf.DictConfig] = None, - cfg_node: Optional[str] = None, - ) -> omegaconf.DictConfig: - """Return a DictConfig containing the resolved configuration values defined in kwargs.""" - kwsconf = OmegaConf.create(kwargs) - if not config: - return kwsconf - # FIXME: IF we resolve at init to get the global conf value we loose the interpolations - is_node = config and cfg_node is not None - resolved_node = config # omegaconf.DictConfig(as_resolved_dict(config)) - if is_node and cfg_node in config: - resolved_node = config[cfg_node] - resolved_with_kws = OmegaConf.merge(kwsconf, resolved_node) - - if is_node: - # node_conf = OmegaConf.create({cfg_node: resolved_with_kws}) - # full_conf = OmegaConf.merge(config, node_conf) - # OmegaConf.resolve(full_conf) - # full_conf = OmegaConf.create(as_resolved_dict(full_conf)) - config[cfg_node] = resolved_with_kws - return config[cfg_node] - return resolved_with_kws - - def _setup_config( - self, - config: Optional[Union[ConfigurationDict, omegaconf.DictConfig]] = None, - cfg_node: Optional[str] = None, - **kwargs, - ): - """Initialize and validate the structured config of target.""" - conf = self._resolve_node(kwargs=kwargs, cfg_node=cfg_node, config=config) - OmegaConf.set_struct(conf, True) - self._target.config = conf # TODO: make param.config constant - - -class Config(BaseConfig): - """ - Config handles the `.conf` attribute of a Configurable class. - - It is analogous to `.param` for param.Parameterized classes. - This class implements all the logic to access and update the config attribute - of a Configurable class, which returns a DictConfig instance that - is automatically update when the parameters of the class change. - """ - - @property - def params(self) -> Dict[str, param.Parameter]: - """Return the param.Parameter dictionary of the target configurable.""" - return self._target.param.params() - - def resolve( - self, - key: Optional[str] = None, - inplace: bool = False, - ) -> Union[Container, ConfigValue, None]: - """Resolve the key attribute of the target Configurable.""" - rsl = super(Config, self).resolve(inplace=inplace) - if not inplace: - value = rsl if key is None else self.to_param_type(key) - return value - - def to_param_type(self, key) -> Any: - """Transform the value of the key target's parameter to a DictConfig compatible type.""" - value = self.select(key) - param_obj = self.params.get(key) - if value == MISSING: - return param_obj.default - # Yaml cannot handle python data types such as tuples, so we cast value - # to the appropriate type after reading data from the the DictConfig - # and before setting the corresponding param.key - if isinstance(value, omegaconf.ListConfig): - value = [x for x in value] - type_ = PARAM_TO_TYPE.get(param_obj.__class__) - if isinstance(param_obj, param.Tuple): - raise ValueError("TUPLE") - if type_ and not isinstance(value, type_): - if value is None: - value = value if param_obj.allow_None else type_() - else: - value = type_(value) - return value - - def dataclass_dict( - self, - ignore: Optional[Union[list, set, tuple, str]] = None, - ) -> DataClassDict: - """Return a dictionary to create a dataclass with the target's parameters.""" - data = {} - ignored = {"name", "config"} if ignore is None else ignore - ignored = set([ignored]) if isinstance(ignored, str) else ignored - for k, v in self.params.items(): - if k in ignored: - continue - for param_type, _type in PARAM_TO_TYPE.items(): - if isinstance(v, param_type): - value = v.default if self._target is None else getattr(self._target, k) - data[k] = (_type, field(default=value)) - break - return data - - def to_dataclass(self) -> type: # DataClass class, not an instance - """Return a dataclass describing the parameter values of the target Configurable.""" - tgt = self._target - name = tgt.__class__.__name__ if isinstance(tgt, Configurable) else tgt.__name__ - dclass = make_dataclass(name, [(k, t, v) for k, (t, v) in self.dataclass_dict().items()]) - return dclass - - def to_dictconfig(self) -> DictConfig: - """Return a structured DictConfig containing the parameters of the target Configurable.""" - return OmegaConf.structured(self.to_dataclass()) - - def sync(self): - """Ensure the parameter values of the target class have the right type.""" - for k in self.config.keys(): - super(Configurable, self._target).__setattr__(k, self.to_param_type(k)) - - def _setup_config( - self, - config: Optional[Union[ConfigurationDict, DictConfig]] = None, - cfg_node: Optional[str] = None, - **kwargs, - ): - """Initialize and validate the structured config of target.""" - ignored = {"name", "config"} - # Make sure the DictConfig is initialized with all the params as keys - kwargs = {k: kwargs.get(k, v.default) for k, v in self.params.items() if k not in ignored} - super(Config, self)._setup_config(config=config, cfg_node=cfg_node, **kwargs) - self.sync() - - -CONF_ATTRS = {"config", "conf", "_conf"} - - -class Configurable(param.Parameterized): - """ - A Configurable class is an extension of param.Parameterized that allows to handle parameters \ - with missing values and omegaconf interpolation strings. - - It add a config attribute containing an omegaconf.DictConfig that contains the values of the - class param.Parameters. - - It also provides a `conf` attribute that allows to access omegaconf functionality - for managing configurations in a similar fashion as the `param` attribute allows to access - param.Parameter functionality. - """ - - config = DictConfig(readonly=False, per_instance=True, instantiate=True) - - def __init__( - self, - config: Optional[Union[ConfigurationDict, DictConfig]] = None, - throw_on_missing: bool = True, - cfg_node: Optional[str] = None, - **kwargs, - ): - """Initialize a Configurable.""" - interp_kwargs = resolve_as_dict(self, kwargs) - super(Configurable, self).__init__(**interp_kwargs) - self.__conf = Config( - self, - config, - cfg_node=cfg_node, - throw_on_missing=throw_on_missing, - **kwargs, - ) - - @property - def conf(self) -> Config: - """Access the Config instance that tracks and manages the values in the class config.""" - return self.__conf - - def __setattr__(self, key, value): - """Update the config values when setting a parameter.""" - is_interp = is_interpolation(value) - if value == MISSING or is_interp: - self.config[key] = value - value = ( - self.conf.to_param_type(key=key) if is_interp else self.param.params()[key].default - ) - # Update the config dict as well as the parameters. Ignored during __init__ of parent class - elif key in self.param.params() and hasattr(self, "conf"): - self.config[key] = value - value = self.conf.to_param_type(key=key) - - super(Configurable, self).__setattr__(key, value) - - def __getattr__(self, item): - """Add support for MISSING values when accessing the parameter values.""" - if item != "config" and OmegaConf.is_missing(self.config, item): - return MISSING - return super(Configurable, self).__getattr__(item) diff --git a/src/mloq/config/custom_click.py b/src/mloq/config/custom_click.py deleted file mode 100644 index a8bf5297..00000000 --- a/src/mloq/config/custom_click.py +++ /dev/null @@ -1,188 +0,0 @@ -""" -This is mostly a copy paste from \ -https://github.com/pallets/click/blob/2fc486c880eda9fdb746ed8baa49416acab9ea6d/src/click/termui.py - -Modified to allow prompt input that has a different color than the prompt text, while keeping -the color of the default prompt values the same as the prompt text color. -""" # noqa: D400 -import io - -from click.exceptions import Abort, UsageError -from click.types import Choice, convert_type -from click.utils import echo, LazyFile - - -# The prompt functions to use. The doc tools currently overwrite these -# functions to customize how they work. -visible_prompt_func = input - -_ansi_reset_all = "\033[0m" - - -def hidden_prompt_func(prompt): - """Input hidden text from the user.""" - import getpass - - return getpass.getpass(prompt) - - -def _build_prompt(text, suffix, show_default=False, default=None, show_choices=True, type=None): - prompt_ = text - if type is not None and show_choices and isinstance(type, Choice): - prompt_ += f" ({', '.join(map(str, type.choices))})" - if default is not None and show_default: - prompt_ = f"{prompt_} [{_format_default(default)}]" - return f"{prompt_}{suffix}" - - -def _format_default(default): - if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): - return default.name - - return default - - -def prompt( - text, - default=None, - hide_input=False, - confirmation_prompt=False, - type=None, - value_proc=None, - prompt_suffix=": ", - show_default=True, - err=False, - show_choices=True, -) -> None: - """Prompts a user for input. This is a convenience function that can \ - be used to prompt a user for input later. - - If the user aborts the input by sending a interrupt signal, this - function will catch it and raise a :exc:`Abort` exception. - - .. versionadded:: 7.0 - Added the show_choices parameter. - - .. versionadded:: 6.0 - Added unicode support for cmd.exe on Windows. - - .. versionadded:: 4.0 - Added the `err` parameter. - - :param text: the text to show for the prompt. - :param default: the default value to use if no input happens. If this - is not given it will prompt until it's aborted. - :param hide_input: if this is set to true then the input value will - be hidden. - :param confirmation_prompt: asks for confirmation for the value. - :param type: the type to use to check the value against. - :param value_proc: if this parameter is provided it's a function that - is invoked instead of the type conversion to - convert a value. - :param prompt_suffix: a suffix that should be added to the prompt. - :param show_default: shows or hides the default value in the prompt. - :param err: if set to true the file defaults to ``stderr`` instead of - ``stdout``, the same as with echo. - :param show_choices: Show or hide choices if the passed type is a Choice. - For example if type is a Choice of either day or week, - show_choices is true and text is "Group by" then the - prompt will be "Group by (day, week): ". - :return: None - """ - result = None - - def prompt_func(text): - f = hidden_prompt_func if hide_input else visible_prompt_func - try: - # Write the prompt separately so that we get nice - # coloring through colorama on Windows - echo(f"{text}{_ansi_reset_all}", nl=False, err=err) - return f("") - except (KeyboardInterrupt, EOFError): - # getpass doesn't print a newline if the user aborts input with ^C. - # Allegedly this behavior is inherited from getpass(3). - # A doc bug has been filed at https://bugs.python.org/issue24711 - if hide_input: - echo(None, err=err) - raise Abort() - - if value_proc is None: - value_proc = convert_type(type, default) - - prompt_ = _build_prompt(text, prompt_suffix, show_default, default, show_choices, type) - - while 1: - while 1: - value = prompt_func(prompt_) - if value: - break - elif default is not None: - value = default - break - try: - result = value_proc(value) - except UsageError as e: - if hide_input: - echo("Error: the value you entered was invalid", err=err) - else: - echo(f"Error: {e.message}", err=err) # noqa: B306 - continue - if not confirmation_prompt: - return result - while 1: - value2 = prompt_func("Repeat for confirmation: ") - if value2: - break - if value == value2: - return result - echo("Error: the two entered values do not match", err=err) - - -def confirm( - text, - default=False, - abort=False, - prompt_suffix=": ", - show_default=True, - err=False, -) -> bool: - """Prompts for confirmation (yes/no question). - - If the user aborts the input by sending a interrupt signal this - function will catch it and raise a :exc:`Abort` exception. - - .. versionadded:: 4.0 - Added the `err` parameter. - - :param text: the question to ask. - :param default: the default for the prompt. - :param abort: if this is set to `True` a negative answer aborts the - exception by raising :exc:`Abort`. - :param prompt_suffix: a suffix that should be added to the prompt. - :param show_default: shows or hides the default value in the prompt. - :param err: if set to true the file defaults to ``stderr`` instead of - ``stdout``, the same as with echo. - :return: User's decision. - """ - prompt_ = _build_prompt(text, prompt_suffix, show_default, "Y/n" if default else "y/N") - while 1: - try: - # Write the prompt separately so that we get nice - # coloring through colorama on Windows - echo(f"{prompt_}{_ansi_reset_all}", nl=False, err=err) - value = visible_prompt_func("").lower().strip() - except (KeyboardInterrupt, EOFError): - raise Abort() - if value in ("y", "yes"): - rv = True - elif value in ("n", "no"): - rv = False - elif value == "": - rv = default - else: - echo("Error: invalid input", err=err) - continue - break - if abort and not rv: - raise Abort() - return rv diff --git a/src/mloq/config/param_patch.py b/src/mloq/config/param_patch.py deleted file mode 100644 index d619de3d..00000000 --- a/src/mloq/config/param_patch.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Patch param to allow omegaconf Missing values and interpolation strings.""" -from omegaconf import MISSING -import param as param_ - - -__DEFAULT_MARK = "__DEFAULT_MARK__" - - -def __init__patched(self, default=__DEFAULT_MARK, **kwargs): - """Handle missing values and interpolations trings when initializing a param.Parameter.""" - from mloq.config.configuration import is_interpolation - - is_missing = default == MISSING - interpolated = is_interpolation(default) - self._missing_init = is_missing - self._interpolation_init = interpolated - if is_missing or interpolated: - kwargs["allow_None"] = True - default = None - elif default == __DEFAULT_MARK: - param_class = getattr(param_, self.__class__.__name__) - default = param_class().default - super(self.__class__, self).__init__(default=default, **kwargs) - - -def _create_param__(item): - """Patch the target param.Parameter to support missing values and interpolations strings.""" - base = getattr(param_, item) - patched_class = type( - item, - (base,), - { - "__init__": __init__patched, - "__slots__": list(base.__slots__) + ["_missing_init", "_interpolation_init"], - }, - ) - return patched_class - - -PATCHED_PARAMETERS = {"String", "Integer", "Number", "Tuple", "Dict", "List", "ListSelector"} - - -class __ParamPatcher: - """Patch the param package to handle missing values and interpolation strings.""" - - PATCHED_PARAMETERS = PATCHED_PARAMETERS - - def __getattr__(self, item): - """Patch the parameters included in PATCHED_PARAMETERS.""" - if item in PATCHED_PARAMETERS: - return _create_param__(item) - return getattr(param_, item) - - def __setattr__(self, key, value): - """Read only monkeypatching.""" - raise NotImplementedError - - -param = __ParamPatcher() diff --git a/src/mloq/config/prompt.py b/src/mloq/config/prompt.py deleted file mode 100644 index 6e7e715a..00000000 --- a/src/mloq/config/prompt.py +++ /dev/null @@ -1,321 +0,0 @@ -"""This file contains the logic defining all the parameters needed to \ -set up a project with mloq.""" -from typing import Any, Dict, List, Optional, Set, Tuple, Union - -import click -from omegaconf import DictConfig, MISSING, OmegaConf -import param - -from mloq.config.configuration import as_resolved_dict, Configurable -from mloq.config.custom_click import confirm, prompt -from mloq.failure import MissingConfigValue - - -Choices = Union[List[str], Tuple[str], Set[str]] - - -class PromptParam: - """ - Defines a configuration parameter. - - It allows to parse a configuration value from different sources in the following order: - 1. Environment variable named as MLOQ_PARAM_NAME - 2. Values defined in mloq.yaml - 3. Interactive promp from CLI (Optional) - """ - - def __init__(self, name: str, target: Configurable, **kwargs): - """ - Initialize a ConfigParam. - - Args: - name: Name of the parameter (as defined in mloq.yaml). - text: Text that will be prompted in the CLI when using interactive mode. - **kwargs: Passed to click.prompt when running in interactive mode. - """ - self.name = name - self._target = target - prompt_text = self.param.doc if self.param.doc else self.name - self._prompt_text = click.style(f"> {prompt_text}", fg="bright_magenta", reset=False) - self._prompt_kwargs = kwargs - self._prompt_kwargs["show_default"] = kwargs.get("show_default", True) - self._prompt_kwargs["type"] = kwargs.get("type", str) - - @property - def param(self) -> param.Parameter: - """Get the param.Parameter object corresponding to the current configuration parameter.""" - return getattr(self._target.param, self.name) - - @property - def value(self) -> Any: - """Return the value of the configuration parameter.""" - return getattr(self._target, self.name) - - @property - def config(self) -> Any: - """Return the value of the parameter as defined in its config DictConfig.""" - if self.name not in self._target.config: - raise MissingConfigValue(f"Config value {self.name} is not defined in config") - elif OmegaConf.is_missing(self._target.config, self.name): - return MISSING - return self._target.config[self.name] - - def __call__( - self, - interactive: bool = False, - default: Optional[Any] = None, - **kwargs, - ): - """ - Return the value of the parameter parsing it from the different input sources available. - - Args: - interactive: Prompt the user to input the value from CLI if it's not defined - in config or as en environment variable. - default: Default value displayed in the interactive mode. - **kwargs: Passed to click.prompt in interactive mode. Overrides the - values defined in __init__ - - Returns: - Value of the parameter. - """ - value = default if default is not None else self.value - value = self._prompt(value, **kwargs) - return value - - def _prompt(self, value, **kwargs): - """Prompt user for value.""" - _kwargs = dict(self._prompt_kwargs) - _kwargs.update(kwargs) - if value is not None: - _kwargs["default"] = value - return prompt(self._prompt_text, **_kwargs) - - -class MultiChoicePrompt(PromptParam): - """ - Define a configuration parameter that can take multiple values \ - from a pre-defined set of values. - - It allows to parse a configuration value from different sources in the following order: - 1. Environment variable named as MLOQ_PARAM_NAME - 2. Values defined in mloq.yaml - 3. Interactive promp from CLI (Optional) - """ - - def __init__( - self, - name: str, - target: Configurable, - choices: Optional[Choices] = None, - **kwargs, - ): - """ - Initialize a ConfigParam. - - Args: - name: Name of the parameter (as defined in mloq.yaml). - choices: Contains all the available values for the parameter. - text: Text that will be prompted in the CLI when using interactive mode. - **kwargs: Passed to click.prompt when running in interactive mode. - """ - kwargs["type"] = str - super(MultiChoicePrompt, self).__init__(name=name, target=target, **kwargs) - self.choices = choices # TODO: use this to validate user input. - - def _prompt(self, value, **kwargs) -> List[str]: - """Transform the parsed string from the CLI into a list of selected values.""" - val = super(MultiChoicePrompt, self)._prompt(value, **kwargs) - return self._parse_string(val) if isinstance(val, str) else val - - @staticmethod - def _parse_string(value) -> List[str]: - def filter_str(s): - return s.lstrip().replace("'", "").replace('"', "").replace("[", "").replace("]", "") - - return [filter_str(s) for s in value.split(",")] - - -class StringPrompt(PromptParam): - """ - Define a configuration parameter that can take a string value. - - It allows to parse a configuration value from different sources in the following order: - 1. Environment variable named as MLOQ_PARAM_NAME - 2. Values defined in mloq.yaml - 3. Interactive promp from CLI (Optional) - """ - - def __init__( - self, - name: str, - target: Configurable, - **kwargs, - ): - """ - Initialize a ConfigParam. - - Args: - name: Name of the parameter (as defined in mloq.yaml). - choices: Contains all the available values for the parameter. - text: Text that will be prompted in the CLI when using interactive mode. - **kwargs: Passed to click.prompt when running in interactive mode. - """ - kwargs["type"] = str - super(StringPrompt, self).__init__(name=name, target=target, **kwargs) - - -class IntPrompt(PromptParam): - """ - Define a configuration parameter that can take an integer value. - - It allows to parse a configuration value from different sources in the following order: - 1. Environment variable named as MLOQ_PARAM_NAME - 2. Values defined in mloq.yaml - 3. Interactive promp from CLI (Optional) - """ - - def __init__( - self, - name: str, - target: Configurable, - **kwargs, - ): - """ - Initialize a ConfigParam. - - Args: - name: Name of the parameter (as defined in mloq.yaml). - choices: Contains all the available values for the parameter. - text: Text that will be prompted in the CLI when using interactive mode. - **kwargs: Passed to click.prompt when running in interactive mode. - """ - kwargs["type"] = int - super(IntPrompt, self).__init__(name=name, target=target, **kwargs) - - -class FloatPrompt(PromptParam): - """ - Define a configuration parameter that can take a floating point value. - - It allows to parse a configuration value from different sources in the following order: - 1. Environment variable named as MLOQ_PARAM_NAME - 2. Values defined in mloq.yaml - 3. Interactive promp from CLI (Optional) - """ - - def __init__( - self, - name: str, - target: Configurable, - **kwargs, - ): - """ - Initialize a ConfigParam. - - Args: - name: Name of the parameter (as defined in mloq.yaml). - choices: Contains all the available values for the parameter. - text: Text that will be prompted in the CLI when using interactive mode. - **kwargs: Passed to click.prompt when running in interactive mode. - """ - kwargs["type"] = float - super(FloatPrompt, self).__init__(name=name, target=target, **kwargs) - - -class BooleanPrompt(PromptParam): - """ - Defines a boolean configuration parameter. - - It allows to parse a configuration value from different sources in the following order: - 1. Environment variable named as MLOQ_PARAM_NAME - 2. Values defined in mloq.yaml - 3. Interactive promp from CLI (Optional) - """ - - def _prompt(self, value, **kwargs): - """Prompt user for value.""" - _kwargs = dict(self._prompt_kwargs) - _kwargs.update(kwargs) - if "type" in _kwargs: - del _kwargs["type"] - if value is not None: - _kwargs["default"] = value - return confirm(self._prompt_text, **_kwargs) - - -PARAM_TO_PROMPT = { - param.Boolean: BooleanPrompt, - param.Integer: IntPrompt, - param.Number: FloatPrompt, - param.String: StringPrompt, - param.ListSelector: MultiChoicePrompt, - # TODO: MultiChoice, Choice -} - - -class Prompt: - """ - Manage all the functionality needed to display a cli prompt. - - It allows to interactively define the values of the different parameters of a class. - """ - - def __init__(self, target: "Promptable"): - """Initialize a Prompt.""" - self._target = target - self._prompts = {} - self._init_prompts() - - def __call__(self, key: str, inplace: bool = False, **kwargs) -> Any: - """Display the a prompt to interactively define the parameter values of target.""" - return self.prompt(key=key, inplace=inplace, **kwargs) - - def _init_prompts(self) -> None: - """Initialize the prompts corresponding to the target Promptable parameters.""" - self._prompts = {} - conf: DictConfig = self._target.config - param_ = self._target.param - for name, value in as_resolved_dict(conf).items(): - param_inst = getattr(param_, name) - type_ = type(param_inst) - prompt_cls = PARAM_TO_PROMPT.get(type_) - if prompt_cls is not None: - default = value if value is not MISSING else param_inst.default - self._prompts[name] = prompt_cls(name, self._target, default=default) - - def prompt(self, key: str, inplace: bool = False, **kwargs) -> Any: - """Display the a prompt to interactively define the parameter values of target.""" - val = self._prompts[key](**kwargs) - if inplace: - setattr(self._target, key, val) - else: - return val - - def prompt_all(self, inplace: bool = False, **kwargs) -> Dict[str, Any]: - """ - Prompt all the target's parameters. - - Return a dictionary containing the provided values. - """ - - def param_precedence(x): - val = getattr(self._target.param, x).precedence - return (1e100 if val is None else val), x - - sorted_keys = sorted(self._prompts.keys(), key=param_precedence) - return {k: self.prompt(key=k, inplace=inplace, **kwargs) for k in sorted_keys} - - -class Promptable(Configurable): - """ - Configurable class that allows to define the parameter values interactively using CLI prompts. - - It contains a prompt attribute in charge of managing the prompting functionality for the - param.Parameters defined. - """ - - def __init__(self, **kwargs): - """Initialize a Promptable.""" - super(Promptable, self).__init__(**kwargs) - self.prompt = Prompt(self) diff --git a/src/mloq/core.py b/src/mloq/core.py new file mode 100644 index 00000000..ec5a84c3 --- /dev/null +++ b/src/mloq/core.py @@ -0,0 +1,9 @@ +def compute(args): + """Compute a placeholder for the compute function. + + Example: + >>> compute(["1", "2", "3"]) + '1' + + """ + return max(args, key=len) diff --git a/src/mloq/custom_click.py b/src/mloq/custom_click.py deleted file mode 100644 index a8bf5297..00000000 --- a/src/mloq/custom_click.py +++ /dev/null @@ -1,188 +0,0 @@ -""" -This is mostly a copy paste from \ -https://github.com/pallets/click/blob/2fc486c880eda9fdb746ed8baa49416acab9ea6d/src/click/termui.py - -Modified to allow prompt input that has a different color than the prompt text, while keeping -the color of the default prompt values the same as the prompt text color. -""" # noqa: D400 -import io - -from click.exceptions import Abort, UsageError -from click.types import Choice, convert_type -from click.utils import echo, LazyFile - - -# The prompt functions to use. The doc tools currently overwrite these -# functions to customize how they work. -visible_prompt_func = input - -_ansi_reset_all = "\033[0m" - - -def hidden_prompt_func(prompt): - """Input hidden text from the user.""" - import getpass - - return getpass.getpass(prompt) - - -def _build_prompt(text, suffix, show_default=False, default=None, show_choices=True, type=None): - prompt_ = text - if type is not None and show_choices and isinstance(type, Choice): - prompt_ += f" ({', '.join(map(str, type.choices))})" - if default is not None and show_default: - prompt_ = f"{prompt_} [{_format_default(default)}]" - return f"{prompt_}{suffix}" - - -def _format_default(default): - if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): - return default.name - - return default - - -def prompt( - text, - default=None, - hide_input=False, - confirmation_prompt=False, - type=None, - value_proc=None, - prompt_suffix=": ", - show_default=True, - err=False, - show_choices=True, -) -> None: - """Prompts a user for input. This is a convenience function that can \ - be used to prompt a user for input later. - - If the user aborts the input by sending a interrupt signal, this - function will catch it and raise a :exc:`Abort` exception. - - .. versionadded:: 7.0 - Added the show_choices parameter. - - .. versionadded:: 6.0 - Added unicode support for cmd.exe on Windows. - - .. versionadded:: 4.0 - Added the `err` parameter. - - :param text: the text to show for the prompt. - :param default: the default value to use if no input happens. If this - is not given it will prompt until it's aborted. - :param hide_input: if this is set to true then the input value will - be hidden. - :param confirmation_prompt: asks for confirmation for the value. - :param type: the type to use to check the value against. - :param value_proc: if this parameter is provided it's a function that - is invoked instead of the type conversion to - convert a value. - :param prompt_suffix: a suffix that should be added to the prompt. - :param show_default: shows or hides the default value in the prompt. - :param err: if set to true the file defaults to ``stderr`` instead of - ``stdout``, the same as with echo. - :param show_choices: Show or hide choices if the passed type is a Choice. - For example if type is a Choice of either day or week, - show_choices is true and text is "Group by" then the - prompt will be "Group by (day, week): ". - :return: None - """ - result = None - - def prompt_func(text): - f = hidden_prompt_func if hide_input else visible_prompt_func - try: - # Write the prompt separately so that we get nice - # coloring through colorama on Windows - echo(f"{text}{_ansi_reset_all}", nl=False, err=err) - return f("") - except (KeyboardInterrupt, EOFError): - # getpass doesn't print a newline if the user aborts input with ^C. - # Allegedly this behavior is inherited from getpass(3). - # A doc bug has been filed at https://bugs.python.org/issue24711 - if hide_input: - echo(None, err=err) - raise Abort() - - if value_proc is None: - value_proc = convert_type(type, default) - - prompt_ = _build_prompt(text, prompt_suffix, show_default, default, show_choices, type) - - while 1: - while 1: - value = prompt_func(prompt_) - if value: - break - elif default is not None: - value = default - break - try: - result = value_proc(value) - except UsageError as e: - if hide_input: - echo("Error: the value you entered was invalid", err=err) - else: - echo(f"Error: {e.message}", err=err) # noqa: B306 - continue - if not confirmation_prompt: - return result - while 1: - value2 = prompt_func("Repeat for confirmation: ") - if value2: - break - if value == value2: - return result - echo("Error: the two entered values do not match", err=err) - - -def confirm( - text, - default=False, - abort=False, - prompt_suffix=": ", - show_default=True, - err=False, -) -> bool: - """Prompts for confirmation (yes/no question). - - If the user aborts the input by sending a interrupt signal this - function will catch it and raise a :exc:`Abort` exception. - - .. versionadded:: 4.0 - Added the `err` parameter. - - :param text: the question to ask. - :param default: the default for the prompt. - :param abort: if this is set to `True` a negative answer aborts the - exception by raising :exc:`Abort`. - :param prompt_suffix: a suffix that should be added to the prompt. - :param show_default: shows or hides the default value in the prompt. - :param err: if set to true the file defaults to ``stderr`` instead of - ``stdout``, the same as with echo. - :return: User's decision. - """ - prompt_ = _build_prompt(text, prompt_suffix, show_default, "Y/n" if default else "y/N") - while 1: - try: - # Write the prompt separately so that we get nice - # coloring through colorama on Windows - echo(f"{prompt_}{_ansi_reset_all}", nl=False, err=err) - value = visible_prompt_func("").lower().strip() - except (KeyboardInterrupt, EOFError): - raise Abort() - if value in ("y", "yes"): - rv = True - elif value in ("n", "no"): - rv = False - elif value == "": - rv = default - else: - echo("Error: invalid input", err=err) - continue - break - if abort and not rv: - raise Abort() - return rv diff --git a/src/mloq/failure.py b/src/mloq/failure.py deleted file mode 100644 index 0c4f34b2..00000000 --- a/src/mloq/failure.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Define Failure - the exception to raise when we break.""" - - -class Failure(Exception): - """Raised when a project setup critical error happens.""" - - def __str__(self) -> str: - """Delegate __str__ to the underlying cause if it exists.""" - if self.args or not self.__cause__: - return super().__str__() - return f"{type(self.__cause__).__name__}: {self.__cause__}" - - -class MissingConfigValue(Failure): - """Raised when a parameter is not defined in the mloq DictConfig.""" - - pass diff --git a/src/mloq/files.py b/src/mloq/files.py deleted file mode 100644 index 8bd88ab0..00000000 --- a/src/mloq/files.py +++ /dev/null @@ -1,95 +0,0 @@ -"""This module defines all the different assets accessible from mloq.""" -from pathlib import Path -import sys -from typing import NamedTuple, Optional, Union - - -class File(NamedTuple): - """ - Generates project files. - - This class defines templating files, which will be rendered according - to the user's configuration. Besides, File instances have additional - attributes used for specifying the destination of the generated file. - - Attributes of this class: - name: Name of the templating file. - src: Location of the templating file. - dst: Name of the file generated from the templating file. - description: Short description of the current file. - is_static: Boolean value. If True, the templating file does not - admit render parameters. - """ - - name: str - src: Path - dst: Path - description: str - is_static: bool - - -def file( - name: str, - path: Union[Path, str], - description: Optional[str] = None, - dst: Optional[Union[Path, str]] = None, - is_static: bool = False, -) -> File: - """Define a new asset as a File namedtuple.""" - if description is None: - print("FIXME: %s must have a description" % name, file=sys.stderr) - description = "TODO" - dst = Path(dst) if dst is not None else name - return File( - name=name, - src=Path(path) / name, - dst=dst, - description=description, - is_static=is_static, - ) - - -# Assets paths -ASSETS_PATH = Path(__file__).parent / "assets" -SHARED_ASSETS_PATH = ASSETS_PATH / "shared" -MLOQ_ASSETS_PATH = ASSETS_PATH / "mloq" -REQUIREMENTS_PATH = ASSETS_PATH / "requirements" - -# Mloq files -mloq_yml = file( - "mloq.yaml", - MLOQ_ASSETS_PATH, - "mloq configuration, you can safely remove it if you don't plan to upgrade", - is_static=True, -) -what_mloq_generated = file("WHAT_MLOQ_GENERATED.md", MLOQ_ASSETS_PATH, "this file") -# Requirements files - -requirements = file( - "requirements.txt", - REQUIREMENTS_PATH, - "list of exact versions of the packages on which your project depends", - is_static=True, -) - -dogfood_req = file( - "dogfood.txt", - REQUIREMENTS_PATH, - "list of mock requirements for testing purposes", - is_static=True, -) - -# Shared templates -makefile = file("Makefile", SHARED_ASSETS_PATH, "common make commands for development") - -pyproject_toml = file( - "pyproject.toml", - SHARED_ASSETS_PATH, - "configuration of various development tools: linters, formatters, packaging", -) - - -def read_file(file: File) -> str: - """Return and string with the content of the provided file.""" - with open(file.src, "r") as f: - return f.read() diff --git a/src/mloq/git.py b/src/mloq/git.py deleted file mode 100644 index bb710d2b..00000000 --- a/src/mloq/git.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Setup Git repository for the project.""" - - -from pathlib import Path -import subprocess -from typing import Union - -from omegaconf import DictConfig - -from mloq.failure import Failure - - -def setup_git( - path: Union[Path, str], - config: DictConfig, -) -> None: - """Initialize a Git repository over the generated files.""" - git_init = config.project.git_init - if not git_init: - return - message = config.template.git_message - push = config.project.git_push - branch = config.template.default_branch - project_name = config.template.project_name - owner = config.template.owner - sign_off = config.project.open_source - path = str(path) - try: - _git_cmd(path, "init") - _git_cmd(path, *f"remote add origin ssh://git@github.com/{owner}/{project_name}".split()) - subprocess.run(("pre-commit", "install"), check=True, cwd=path) - - def commit_all(): - _git_cmd(path, *"add .".split()) - _git_cmd(path, *f"commit {'--signoff' if sign_off else ''} -m".split(), message) - - if push: - _git_cmd(path, *f"checkout -b {branch}".split()) - _git_cmd(path, *"add README.md".split()) - _git_cmd( - path, - *f"commit {'--signoff' if sign_off else ''} -m".split(), - f"Initialize {branch}", - ) - _git_cmd(path, *f"push origin {branch}".split()) - - commit_all() - - if push: - _git_cmd(path, *f"push origin HEAD:init-{branch}".split()) - except subprocess.CalledProcessError as e: - raise Failure() from e - - -def _git_cmd(git_dir: str, *parts: str) -> None: - subprocess.run(("git",) + parts, check=True, cwd=git_dir) diff --git a/src/mloq/record.py b/src/mloq/record.py deleted file mode 100644 index 14371356..00000000 --- a/src/mloq/record.py +++ /dev/null @@ -1,148 +0,0 @@ -"""This module contains the classes that keep track of the internal state of the\ - application when running a Command.""" -from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union - -from omegaconf import DictConfig, OmegaConf - -from mloq.files import File - - -class Ledger: - """Keep track of the generated files.""" - - def __init__(self): - """Initialize a new instance of the Ledger class.""" - self._files = [] - - @property - def files(self) -> List[Tuple[str, str]]: - """Return the list of generated file names.""" - return [(str(f), d) for f, d in sorted(self._files)] - - def register(self, file: Union[File, str, Path], description: Optional[str] = None) -> None: - """Append another generated file to the book.""" - if isinstance(file, File): - description = file.description if description is None else description - file = file.dst - elif description is None: - raise ValueError("description is None. Please provide a file description") - self._files.append((Path(file), description)) - - -class CMDRecord: - """ - Keep track of files and directories that will be created by mloq. - - The :class:`CMDRecord` acts as a single source of truth for storing the - files and directories generated by `mloq` as well as the necessary configuration - to generate them. - - This class registers three separate data sources: - - - `config`: - An :class:`omegaconf.DictConfig` that contains the configuration of - all the commands executed by `mloq`. - - - `files`: - A `dict` containing the content and location of the files generated by mloq. - It is indexed by :class:`Path` objects that indicate where the file will be created, and - its values are instances of :class:`mloq.files.File`. - - - `directories`: - List of :class:`Path` instances pointing to the different directories - that `mloq` will create. - - This class is initialized from a configuration dictionary. The dictionary - can be either an :class:`omegaconf.DictConfig` or an empty dictionary. - """ - - def __init__( - self, - config: Optional[DictConfig] = None, - files: Optional[Dict[Path, File]] = None, - directories: Optional[List[Path]] = None, - ): - """ - Initialize a new instance of :class:`CMDRecord`. - - Args: - config: DictConfig element that stores the configuration parameters. - files: Dictionary that registers the files generated by mloq. Keys - are Paths strings labeling the location where the file will - be created. Values are File elements referencing the current - file. - directories: List that stores the directories that will be generated - by the mloq according to the user's configuration. - """ - self._files = {} if files is None else files - self._directories: List[Path] = [] if directories is None else directories - self._config: DictConfig = DictConfig({}) if config is None else config - - @property - def config(self) -> DictConfig: - """ - Store the configuration parameters that govern the project's structure. - - It contains one configuration entry per each :class:`Command` that will be run, - and each entry will only be modified by the :class:`Command` it represents. Each - :class:`Command` instance is responsible for updating its corresponding - `config` values. - - Returns: - :class:`omegaconf.DictConfig` that contains the configuration of - all the commands executed by `mloq`. - """ - return self._config - - @property - def files(self) -> Dict[Path, File]: - """ - Return the dictionary of files used by mloq to generate the project configuration. - - `files` is a `dict` containing the content and location of the files generated by mloq. - It is indexed by :class:`Path` objects that indicate where the file will be created, and - its values are instances of :class:`mloq.files.File`. - - Each different :class:`Command` is responsible for registering the files it generates - to the `files` dictionary. - """ - return self._files - - @property - def directories(self) -> List[Path]: - """Contain the folders that will be created by mloq for storing the project's files.""" - return self._directories - - def update_config(self, config: DictConfig) -> None: - """Update the configuration attribute according to the values entered by the user.""" - self._config = OmegaConf.merge(self._config, config) - - def register_file( - self, - file: File, - path: Union[Path, str], - description: Optional[str] = None, - ) -> None: - """ - Append a new file to the 'files' container. - - Keys are Path strings describing the location where the file will - be created. Values are File objects containing the information - of the file that will be generated. - """ - if description is None and not file.description: - raise ValueError("File description cannot be None. Please provide a description.") - elif description is not None: - file = File( - name=file.name, - src=file.src, - dst=file.dst, - description=description, - is_static=file.is_static, - ) - self.files[Path(path) / file.dst] = file - - def register_directory(self, path: Union[Path, str]) -> None: - """Append a new directory path to the 'directories' container.""" - self.directories.append(Path(path)) diff --git a/src/mloq/runner.py b/src/mloq/runner.py deleted file mode 100644 index e25cb982..00000000 --- a/src/mloq/runner.py +++ /dev/null @@ -1,120 +0,0 @@ -"""This module defines the pipeline for running a mloq command, such as config loading, \ -template writing and interfacing with click.""" -from pathlib import Path -import sys -from typing import Callable, Union -from unittest.mock import patch - -import hydra -from omegaconf import DictConfig, OmegaConf - -from mloq import _logger -from mloq.command import Command -from mloq.files import mloq_yml -from mloq.record import CMDRecord -from mloq.writer import Writer - - -def load_config(config_file: Union[Path, str], hydra_args: str) -> DictConfig: - """ - Load the necessary configuration for running mloq from a mloq.yaml file. - - If no path to mloq.yaml is provided, it returns a template to be filled in - using the interactive mode. - - Args: - config_file: Path to the target mloq.yaml file. - hydra_args: Arguments passed to hydra for composing the project configuration. - - Returns: - DictConfig containing the project configuration. - """ - config_file = Path(config_file) if config_file else Path() / mloq_yml.dst - config_file = ( - config_file - if (config_file.exists() and config_file.is_file()) - else config_file / mloq_yml.dst - ) - if config_file.exists() and config_file.is_file(): - _logger.info(f"Loading config file from {config_file}") - hydra_args = ["--config-dir", str(config_file.parent)] + list(hydra_args) - config = DictConfig({}) - - @hydra.main(config_path=".", config_name=config_file.name) - def load_config(loaded_config: DictConfig): - nonlocal config - config = loaded_config - - with patch("sys.argv", [sys.argv[0]] + list(hydra_args)): - load_config() - else: - _logger.info("No mloq.yaml file provided. Creating a new configuration") - config = OmegaConf.load(mloq_yml.src) - return config - - -def write_record( - record: CMDRecord, - path: Union[Path, str], - overwrite: bool = False, - only_config: bool = False, -) -> None: - """ - Write the contents of the provided record to the target path. - - The writing process is performed by :class: `Writer`, class that fills - in rendered templates according to the given configuration. - - Args: - record: CMDRecord containing all the data to be written. - path: Target directory to write the data. - overwrite: If True overwrite existing files. - only_config: Do not write any file except mloq.yaml - - Returns: - None. - """ - if only_config: - with open(Path(path) / mloq_yml.dst, "w") as f: - OmegaConf.save(config=record.config, f=f) - else: - writer = Writer(record=record, overwrite=overwrite, path=path) - writer.run() - - -def run_command(cmd_cls, use_click: bool = True) -> Callable: - """ - Run the given Command class. - - Args: - cmd_cls: Command to be executed. - use_click: If True run the function as a "cli" command. - - Returns: - A function that will run the target class as a mloq command. - """ - from mloq.cli import mloq_click_command - - def _run_command( - config_file: str, - output_directory: str, - overwrite: bool, - only_config: bool, - interactive: bool, - hydra_args: str, - ) -> None: - config: DictConfig = load_config(config_file=config_file, hydra_args=hydra_args) - record = CMDRecord(config=config) - cmd: Command = cmd_cls(record=record, interactive=interactive) - record = cmd.run() - write_record( - record=record, - path=output_directory, - overwrite=overwrite, - only_config=only_config, - ) - - if use_click: - _run_command = mloq_click_command(_run_command) - _run_command.__name__ = cmd_cls.cmd_name - return _run_command diff --git a/src/mloq/templating.py b/src/mloq/templating.py deleted file mode 100644 index 5d4fe08b..00000000 --- a/src/mloq/templating.py +++ /dev/null @@ -1,71 +0,0 @@ -"""This module defines common functionality for rendering and writing File templates.""" -from datetime import datetime -import os -from pathlib import Path -from typing import Any, Mapping, Union - -from jinja2 import Environment, FileSystemLoader, select_autoescape -from omegaconf import DictConfig - -from mloq import _logger -from mloq.files import ASSETS_PATH, File, read_file -from mloq.record import Ledger - - -jinja_env = Environment( - loader=FileSystemLoader([str(ASSETS_PATH / x) for x in os.listdir(ASSETS_PATH)]), - # loader=PackageLoader("mloq", "assets"), - autoescape=select_autoescape(["html", "xml"]), - keep_trailing_newline=True, -) - - -def render_template(file: File, kwargs: Mapping[str, Any]) -> str: - """ - Render a jinja template with the provided parameter dict. - - Args: - file: File object representing the jinja template that will be rendered. - kwargs: Dictionary containing the parameters key and corresponding values - that will be used to render the template. - - Returns: - String containing the rendered template. - """ - if file.is_static: - return read_file(file) - jinja_template = jinja_env.get_template(str(file.name)) - jinja_template.globals["now"] = datetime.now - return jinja_template.render(**kwargs) - - -def write_template( - file: File, - config: DictConfig, - path: Union[Path, str], - ledger: Ledger, - overwrite: bool = False, -): - """ - Create new file containing the rendered template found in source_path. - - Args: - file: File object representing the jinja template that will be rendered. - config: OmegaConf dictionary containing the parameters key and corresponding values - that will be used to render the templates. - path: Absolute path to the folder where the file will be written. - ledger: Book keeper to keep track of the generated files. - overwrite: If False, copy the file if it does not already exists in the - target path. If True, overwrite the target file if it is already present. - - Returns: - None. - """ - if not overwrite and path.exists(): - _logger.debug(f"file {file.dst} already exists. Skipping") - return - - ledger.register(file, description=file.description) - rendered = render_template(file, config) - with open(path, "w") as f: - f.write(rendered) diff --git a/src/mloq/version.py b/src/mloq/version.py index 49db098c..3dc1f76b 100644 --- a/src/mloq/version.py +++ b/src/mloq/version.py @@ -1,2 +1 @@ -"""Current version of the project. Do not modify manually.""" -__version__ = "0.0.75" +__version__ = "0.1.0" diff --git a/src/mloq/writer.py b/src/mloq/writer.py deleted file mode 100644 index 28226ee2..00000000 --- a/src/mloq/writer.py +++ /dev/null @@ -1,123 +0,0 @@ -"""The writer module defines the Writer class, which is in charge of creating the files \ -and directories specified in the CMDRecord.""" -import os -from pathlib import Path -from typing import Union - -from omegaconf import DictConfig - -from mloq.files import File, what_mloq_generated -from mloq.record import CMDRecord, Ledger -from mloq.templating import write_template - - -class Writer: - """ - Write all the files specified on the provided CMDRecord. - - This class fills in rendered templates according to the provided - configuration and generates the resulting file on the specified - folder. - - Attributes of this class: - path: Path string describing the destination folder where the - file will be created. - ledger: Instance of the Ledger class. It contains a dictionary - summarizing the files that will by generated by mloq. - record: Instance of the CMDRecord class. It keeps track of all - files and directories that will be created from the - user's configuration. - overwrite: Boolean value. If True, existing files will be rewritten - by mloq application. - """ - - def __init__(self, path: Union[Path, str], record: CMDRecord, overwrite: bool = False): - """ - Initialize a new instance of the CMDRecord class. - - The class is instantiated from a CMDRecord object, which keeps - of all files and directories that will be created by mloq. - - Args: - path: Path string describing the location where the files - will be generated. - record: CMDRecord instance. Register the files and directories - that will be created by mloq according to the configuration - provided by the user. - overwrite: Boolean value. If True, all existing files will be - rewritten by the mloq application. - """ - self._record = record - self._ledger = Ledger() - self._path = Path(path) # Path to the project root directory - self.overwrite = overwrite - - @property - def path(self) -> Path: - """Path string describing the location where the files will be generated.""" - return self._path - - @property - def ledger(self) -> Ledger: - """Keep track of the generated files.""" - return self._ledger - - @property - def record(self) -> CMDRecord: - """Register the files and directories generated by the mloq application.""" - return self._record - - def create_directories(self) -> None: - """Create the folders registered inside the attribute 'record.directories'.""" - for directory in self.record.directories: - os.makedirs(self.path / directory, exist_ok=True) - - def write_templates(self) -> None: - """Generate the files recorded in the attribute 'record.files' on the specified path.""" - for path, file in self.record.files.items(): - self.write_template(file=file, path=path, config=self.record.config) - - def dump_ledger(self) -> None: - """ - Write the summary of the generated files. - - This method collects the elements stored in ledger to create a markdown - document that summarizes all the files generated by the MLOQ application. - """ - config = DictConfig({**self.record.config, "generated_files": self.ledger.files}) - self.write_template( - what_mloq_generated, - path=what_mloq_generated.dst, - config=config, - ) - - def write_template( - self, - file: File, - path: Path, - config: DictConfig, - ) -> None: - """ - Create new file containing the rendered template configuration. - - Args: - file: File object representing the jinja template that will be rendered. - path: Target folder where the generated files will be written. - config: DictConfig containing the selected project configuration. - - Returns: - None. - """ - write_template( - file=file, - config=config, - path=self.path / path, - overwrite=self.overwrite, - ledger=self.ledger, - ) - - def run(self) -> None: - """Generate all files and directories registered inside the record instance.""" - self.create_directories() - self.write_templates() - self.dump_ledger() diff --git a/tests/__init__.py b/tests/__init__.py index d76c69af..e69de29b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +0,0 @@ -from tests.test_command import TestCommand diff --git a/tests/commands/__init__.py b/tests/commands/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/commands/test_ci.py b/tests/commands/test_ci.py deleted file mode 100644 index a57f2887..00000000 --- a/tests/commands/test_ci.py +++ /dev/null @@ -1,159 +0,0 @@ -import os -from pathlib import Path -import tempfile - -from omegaconf import DictConfig, OmegaConf -import pytest - -from mloq.commands.ci import CiCMD, push_python_wkf -from mloq.runner import run_command -from mloq.writer import CMDRecord -from tests import TestCommand # noqa: F401 -from tests.test_runner import dir_trees_are_equal - - -ci_conf = { - "ci": dict( - bot_name="test_bot_name", - bot_email="test_bot_email", - disable=False, - vendor="test_vendor", - python_versions=["3.8"], - # ci_ubuntu_version="ubuntu-20.04", - ci_python_version="3.8", - ubuntu_version="ubuntu-20.04", - ci_extra="", - open_source=True, - project_name="test_name", - default_branch="test_branch", - owner="test_owner", - author="test_author", - email="test_email", - project_url="test_url", - docker=True, - docker_org="test_org", - ), -} - -ci_conf_with_globals = DictConfig( - { - "globals": { - "project_name": "test_name", - "default_branch": "test_branch", - "owner": "test_owner", - "author": "test_author", - "email": "test_email", - "description": "test_description", - "open_source": True, - "project_url": "test_url", - }, - "package": dict( - python_versions=["3.8"], - disable=False, - ), - "docker": dict(docker_org="test_org", disable=False), - "ci": dict( - bot_name="test_bot_name", - bot_email="test_bot_email", - disable=False, - vendor="test_vendor", - python_versions="${package.python_versions}", - ubuntu_version="ubuntu-20.04", - ci_python_version="3.8", - # ci_ubuntu_version="ubuntu-20.04", - ci_extra="", - open_source="${globals.open_source}", - project_name="${globals.project_name}", - default_branch="${globals.default_branch}", - owner="${globals.owner}", - author="${globals.author}", - email="${globals.email}", - project_url="${globals.project_url}", - docker=True, - docker_org="${docker.docker_org}", - ), - }, -) - - -@pytest.fixture(params=[(ci_conf, ci_conf_with_globals)]) -def config_paths(request): - c1, c2 = request.param - temp_path = tempfile.TemporaryDirectory() - conf_1 = DictConfig(c1) - conf_2 = DictConfig(c2) - filepath_1 = Path(temp_path.name) / "mloq1.yaml" - filepath_2 = Path(temp_path.name) / "mloq2.yaml" - with open(filepath_1, "w") as f: - OmegaConf.save(conf_1, f) - with open(filepath_2, "w") as f: - OmegaConf.save(conf_2, f) - yield filepath_1, filepath_2 - temp_path.cleanup() - - -fixture_ids = ["ci-conf-cmd", "ci-conf-globals"] - - -@pytest.fixture( - params=[(CiCMD, ci_conf), (CiCMD, ci_conf_with_globals)], - scope="function", - ids=fixture_ids, -) -def command_and_config(request): - command_cls, conf_dict = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, config - - -example_files = { - Path(".github") / "workflows" / push_python_wkf.dst: push_python_wkf, -} - - -@pytest.fixture( - params=[(CiCMD, ci_conf, example_files), (CiCMD, ci_conf_with_globals, example_files)], - scope="function", - ids=fixture_ids, -) -def command_and_example(request): - command_cls, conf_dict, example = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, example - - -class TestCi: - def test_name_is_correct(self, command_and_config): - command, config = command_and_config - assert command.cmd_name == "ci" - - def test_equivalent_configs(self, config_paths): - path_conf_1, path_conf_2 = config_paths - temp_path = tempfile.TemporaryDirectory() - temp_path1 = Path(temp_path.name) / "target1" - temp_path2 = Path(temp_path.name) / "target2" - os.makedirs(temp_path1) - os.makedirs(temp_path2) - _run_cmd = run_command(cmd_cls=CiCMD, use_click=False) - _run_cmd( - config_file=path_conf_1, - output_directory=temp_path1, - overwrite=False, - only_config=False, - interactive=False, - hydra_args="", - ) - _run_cmd( - config_file=path_conf_2, - output_directory=temp_path2, - overwrite=False, - only_config=False, - interactive=False, - hydra_args="", - ) - assert dir_trees_are_equal(str(temp_path1), str(temp_path2)) - temp_path.cleanup() diff --git a/tests/commands/test_docker.py b/tests/commands/test_docker.py deleted file mode 100644 index 68d07dda..00000000 --- a/tests/commands/test_docker.py +++ /dev/null @@ -1,112 +0,0 @@ -from pathlib import Path - -from omegaconf import DictConfig, OmegaConf -import pytest - -from mloq.commands.docker import DOCKER_FILES, DockerCMD -from mloq.writer import CMDRecord -from tests.test_command import TestCommand - - -docker_conf = { - "docker": dict( - disable=False, - project_name="test_project", - cuda="???", - cuda_image_type="cudnn8-runtime-ubuntu20.04", - cuda_version="11.2", - python_version="3.8", - ubuntu_version="20.04", - base_image="???", - test=True, - lint=True, - jupyter=True, - jupyter_password="test_password", - docker_org="testorg", - requirements=[], - makefile=True, - ), -} - -docker_conf_with_globals = DictConfig( - { - "globals": { - "project_name": "test project", - "default_branch": "test_branch", - "owner": "test_owner", - "author": "test_author", - "email": "test_email", - "description": "test_description", - "open_source": True, - "project_url": "???", - }, - "requirements": ["tensorflow"], - "docker": dict( - disable=False, - project_name="${globals.project_name}", - cuda="???", - cuda_image_type="cudnn8-runtime-ubuntu20.04", - cuda_version="11.2", - python_version="3.8", - ubuntu_version="20.04", - base_image="???", - test=True, - lint=True, - jupyter=True, - jupyter_password="test_password", - docker_org="testorg", - requirements="${requirements}", - makefile=True, - ), - }, -) - -fixture_ids = ["docker-conf-cmd", "docker-conf-globals"] - - -@pytest.fixture( - params=[(DockerCMD, docker_conf), (DockerCMD, docker_conf_with_globals)], - scope="function", - ids=fixture_ids, -) -def command_and_config(request): - command_cls, conf_dict = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, config - - -def example_files(): - - return {Path() / f.dst: f for f in DOCKER_FILES} - - -cmd_examples_param = [ - (DockerCMD, docker_conf, example_files()), - (DockerCMD, docker_conf_with_globals, example_files()), -] - - -@pytest.fixture(params=cmd_examples_param, scope="function", ids=fixture_ids) -def command_and_example(request): - command_cls, conf_dict, example = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, example - - -class TestDockerCMD: - def test_name_is_correct(self, command_and_config): - command, config = command_and_config - assert command.cmd_name == "docker" - - def test_parse_cfg_base_image(self, command_and_config): - command, config = command_and_config - - # assert command.conf.is_missing("base_image"), config.docker - command.parse_config() - assert command.base_image is not None - assert command.record.config.docker.base_image is not None - assert command.base_image == command.get_base_image() diff --git a/tests/commands/test_docs.py b/tests/commands/test_docs.py deleted file mode 100644 index ca60c4a8..00000000 --- a/tests/commands/test_docs.py +++ /dev/null @@ -1,107 +0,0 @@ -from pathlib import Path - -from omegaconf import DictConfig -import pytest - -from mloq.commands.docs import ( - conf_py, - deploy_docs, - docs_req, - DocsCMD, - index_md, - make_bat_docs, - makefile_docs, -) -from mloq.writer import CMDRecord - - -dummy_project_url = "https://test.com" -docs_conf = { - "docs": dict( - disable=False, - deploy_docs=True, - project_name="test_project", - description="test description", - author="test_author", - copyright_holder="test_copyright_holder", - copyright_year="1990", - default_branch="test_branch", - project_url=dummy_project_url, - ), -} - -docs_conf_with_globals = DictConfig( - { - "globals": { - "project_name": "test_name", - "default_branch": "test_branch", - "owner": "test_owner", - "author": "test_author", - "email": "test_email", - "description": "test_description", - "open_source": True, - "project_url": dummy_project_url, - }, - "license": {"copyright_holder": "test_holder", "copyright_year": 1990}, - "docs": dict( - disable=False, - deploy_docs=True, - project_name="${globals.project_name}", - description="${globals.description}", - author="${globals.author}", - copyright_holder="${license.copyright_holder}", - copyright_year="${license.copyright_year}", - ), - }, -) - -fixture_ids = ["docs-conf-cmd", "docs-conf-globals"] - - -@pytest.fixture( - params=[(DocsCMD, docs_conf), (DocsCMD, docs_conf_with_globals)], - scope="function", - ids=fixture_ids, -) -def command_and_config(request): - command_cls, conf_dict = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, config - - -def example_files(deploy_docs=True): - docs_path = Path("docs") - source_path = docs_path / "source" - example = { - source_path / conf_py.dst: conf_py, - source_path / index_md.dst: index_md, - docs_path / makefile_docs.dst: makefile_docs, - docs_path / make_bat_docs.dst: make_bat_docs, - docs_path / docs_req.dst: docs_req, - } - if deploy_docs: - example[Path(".github") / "workflows"] = deploy_docs - return example - - -cmd_examples_param = [ - (DocsCMD, docs_conf, example_files()), - (DocsCMD, docs_conf_with_globals, example_files()), -] - - -@pytest.fixture(params=cmd_examples_param, scope="function", ids=fixture_ids) -def command_and_example(request): - command_cls, conf_dict, example = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, example - - -class TestDocs: - def test_name_is_correct(self, command_and_config): - command, config = command_and_config - assert command.cmd_name == "docs" diff --git a/tests/commands/test_globals.py b/tests/commands/test_globals.py deleted file mode 100644 index 7f47d778..00000000 --- a/tests/commands/test_globals.py +++ /dev/null @@ -1,60 +0,0 @@ -from omegaconf import DictConfig -import pytest - -from mloq.commands.globals import GlobalsCMD -from mloq.writer import CMDRecord - - -globals_conf = DictConfig( - { - "globals": { - "project_name": "test name", - "default_branch": "test_branch", - "owner": "test_owner", - "author": "test_author", - "email": "test_email", - "description": "test_description", - "open_source": True, - "project_url": "???", - "use_poetry": False, - "license": "MIT", - }, - }, -) - - -@pytest.fixture(params=[(GlobalsCMD, globals_conf)], scope="function", ids=["globals-conf-cmd"]) -def command_and_config(request): - command_cls, conf_dict = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, config - - -class TestGlobalsCMD: - def test_default_project_url(self, command_and_config): - command, config = command_and_config - record = command.run() - example = "https://github.com/test_owner/test-name" - assert record.config.globals.project_url == example - - def test_non_default_project_url(self): - example = "https://my_custom_url" - non_empty_url = DictConfig( - { - "globals": { - "project_name": "test name", - "default_branch": "test_branch", - "owner": "test_owner", - "author": "test_author", - "email": "test_email", - "description": "test_description", - "open_source": True, - "project_url": example, - }, - }, - ) - cmd = GlobalsCMD(record=CMDRecord(non_empty_url)) - record = cmd.run() - assert record.config.globals.project_url == example diff --git a/tests/commands/test_license.py b/tests/commands/test_license.py deleted file mode 100644 index ca2bfdd8..00000000 --- a/tests/commands/test_license.py +++ /dev/null @@ -1,169 +0,0 @@ -import os -from pathlib import Path -import tempfile - -from omegaconf import DictConfig, OmegaConf -import pytest - -from mloq.commands.license import dco, LicenseCMD, LICENSES -from mloq.runner import run_command -from mloq.writer import CMDRecord -from tests import TestCommand # noqa: F401 -from tests.test_runner import dir_trees_are_equal - - -license_conf = DictConfig( - { - "license": dict( - disable=False, - license="Apache-2.0", - copyright_year=1991, - copyright_holder="test_owner", - project_name="test_name", - project_url="test_url", - email="test_email", - ), - }, -) - -license_conf_with_globals = DictConfig( - { - "globals": { - "project_name": "test_name", - "default_branch": "ll", - "owner": "test_owner", - "author": "test_author", - "email": "test_email", - "description": "supercalifragilisticexpialidocious", - "open_source": True, - "project_url": "test_url", - }, - "license": dict( - disable=False, - license="Apache-2.0", - copyright_year=1991, - copyright_holder="${globals.owner}", - project_name="${globals.project_name}", - project_url="${globals.project_url}", - email="${globals.email}", - ), - }, -) - -license_proprietary = DictConfig( - { - "globals": { - "project_name": "test_name", - "default_branch": "ll", - "owner": "test_owner", - "author": "test_author", - "email": "test_email", - "description": "supercalifragilisticexpialidocious", - "open_source": False, - "project_url": "test_url", - }, - "license": dict( - disable=False, - license="proprietary", # to test that the license is not added - copyright_year=1991, - copyright_holder="${globals.owner}", - project_name="${globals.project_name}", - project_url="${globals.project_url}", - email="${globals.email}", - ), - }, -) - - -example_files = { - Path() / dco.dst: dco, - Path() / LICENSES[license_conf.license.license].dst: LICENSES[license_conf.license.license], -} - -example_files_empty = {Path() / dco.dst: dco} - -fixture_ids = ["license-conf-cmd", "license-conf-globals", "license-proprietary"] - - -@pytest.fixture(params=[(license_conf, license_conf_with_globals)]) -def config_paths(request): - c1, c2 = request.param - temp_path = tempfile.TemporaryDirectory() - conf_1 = DictConfig(c1) - conf_2 = DictConfig(c2) - filepath_1 = Path(temp_path.name) / "mloq1.yaml" - filepath_2 = Path(temp_path.name) / "mloq2.yaml" - with open(filepath_1, "w") as f: - OmegaConf.save(conf_1, f) - with open(filepath_2, "w") as f: - OmegaConf.save(conf_2, f) - yield filepath_1, filepath_2 - temp_path.cleanup() - - -@pytest.fixture( - params=[ - (LicenseCMD, license_conf), - (LicenseCMD, license_conf_with_globals), - (LicenseCMD, license_proprietary), - ], - ids=fixture_ids, - scope="function", -) -def command_and_config(request): - command_cls, conf_dict = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, config - - -@pytest.fixture( - params=[ - (LicenseCMD, license_conf, example_files), - (LicenseCMD, license_conf_with_globals, example_files), - (LicenseCMD, license_proprietary, example_files_empty), - ], - ids=fixture_ids, - scope="function", -) -def command_and_example(request): - command_cls, conf_dict, example = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, example - - -class TestLicense: - def test_name_is_correct(self, command_and_config): - command, config = command_and_config - assert "LicenseCMD" in command.name - assert command.cmd_name == "license" - - def test_equivalent_configs(self, config_paths): - path_conf_1, path_conf_2 = config_paths - temp_path = tempfile.TemporaryDirectory() - temp_path1 = Path(temp_path.name) / "target1" - temp_path2 = Path(temp_path.name) / "target2" - os.makedirs(temp_path1) - os.makedirs(temp_path2) - _run_cmd = run_command(cmd_cls=LicenseCMD, use_click=False) - _run_cmd( - config_file=path_conf_1, - output_directory=temp_path1, - overwrite=False, - only_config=False, - interactive=False, - hydra_args="", - ) - _run_cmd( - config_file=path_conf_2, - output_directory=temp_path2, - overwrite=False, - only_config=False, - interactive=False, - hydra_args="", - ) - assert dir_trees_are_equal(str(temp_path1), str(temp_path2)) - temp_path.cleanup() diff --git a/tests/commands/test_lint.py b/tests/commands/test_lint.py deleted file mode 100644 index 07e6a069..00000000 --- a/tests/commands/test_lint.py +++ /dev/null @@ -1,124 +0,0 @@ -import os -from pathlib import Path -import tempfile - -from omegaconf import DictConfig, OmegaConf -import pytest - -from mloq.commands.lint import lint_req, LintCMD, pyproject_toml -from mloq.runner import run_command -from mloq.writer import CMDRecord -from tests import TestCommand # noqa: F401 -from tests.test_runner import dir_trees_are_equal - - -lint_conf = { - "lint": dict( - disable=False, - docstring_checks=True, - pyproject_extra="", - project_name="test_lint", - ), -} - -lint_conf_with_globals = DictConfig( - { - "globals": { - "project_name": "test_lint", - "default_branch": "ll", - "owner": "test_paco", - "author": "test_fran", - "email": "test_jose@kk.com", - "description": "supercalifragilisticexpialidocious", - "open_source": None, - "project_url": "test_url.net", - }, - "lint": dict( - disable=False, - docstring_checks=True, - pyproject_extra="", - project_name="${globals.project_name}", - ), - }, -) -fixture_ids = ["lint-conf-cmd", "lint-conf-globals"] - -example_files = { - Path() / pyproject_toml.dst: pyproject_toml, - Path() / lint_req.dst: lint_req, -} - - -@pytest.fixture(params=[(lint_conf, lint_conf_with_globals)]) -def config_paths(request): - c1, c2 = request.param - temp_path = tempfile.TemporaryDirectory() - conf_1 = DictConfig(c1) - conf_2 = DictConfig(c2) - filepath_1 = Path(temp_path.name) / "mloq1.yaml" - filepath_2 = Path(temp_path.name) / "mloq2.yaml" - with open(filepath_1, "w") as f: - OmegaConf.save(conf_1, f) - with open(filepath_2, "w") as f: - OmegaConf.save(conf_2, f) - yield filepath_1, filepath_2 - temp_path.cleanup() - - -@pytest.fixture( - params=[(LintCMD, lint_conf), (LintCMD, lint_conf_with_globals)], - scope="function", - ids=fixture_ids, -) -def command_and_config(request): - command_cls, conf_dict = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, config - - -@pytest.fixture( - params=[(LintCMD, lint_conf, example_files), (LintCMD, lint_conf_with_globals, example_files)], - scope="function", - ids=fixture_ids, -) -def command_and_example(request): - command_cls, conf_dict, example = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, example - - -class TestLint: - def test_name_is_correct(self, command_and_config): - command, config = command_and_config - assert command.cmd_name == "lint" - - def test_equivalent_configs(self, config_paths): - path_conf_1, path_conf_2 = config_paths - temp_path = tempfile.TemporaryDirectory() - temp_path1 = Path(temp_path.name) / "target1" - temp_path2 = Path(temp_path.name) / "target2" - os.makedirs(temp_path1) - os.makedirs(temp_path2) - _run_cmd = run_command(cmd_cls=LintCMD, use_click=False) - _run_cmd( - config_file=path_conf_1, - output_directory=temp_path1, - overwrite=False, - only_config=False, - interactive=False, - hydra_args="", - ) - _run_cmd( - config_file=path_conf_2, - output_directory=temp_path2, - overwrite=False, - only_config=False, - interactive=False, - hydra_args="", - ) - assert dir_trees_are_equal(str(temp_path1), str(temp_path2)) - temp_path.cleanup() diff --git a/tests/commands/test_package.py b/tests/commands/test_package.py deleted file mode 100644 index 738312b7..00000000 --- a/tests/commands/test_package.py +++ /dev/null @@ -1,158 +0,0 @@ -import os -from pathlib import Path -import tempfile - -from omegaconf import DictConfig, OmegaConf -import pytest - -from mloq.commands.package import PackageCMD, pyproject_toml, setup_py -from mloq.runner import run_command -from mloq.writer import CMDRecord -from tests import TestCommand # noqa: F401 -from tests.test_runner import dir_trees_are_equal - - -package_conf = DictConfig( - { - "package": dict( - disable=False, - project_name="test_package", - description="configuration for package tests", - default_branch="test_branch_package", - project_url="test_url", - owner="test_owner", - author="test_author", - email="test_email", - license="MIT", - python_versions=["3.6", "3.7", "3.8", "3.9"], - pyproject_extra="", - license_classifier="???", - use_poetry=True, - ), - "lint": dict(project_name="${package.project_name}"), - } -) - -package_conf_with_globals = DictConfig( - { - "globals": { - "project_name": "test_package", - "default_branch": "test_branch_package", - "owner": "test_owner", - "author": "test_author", - "email": "test_email", - "description": "configuration for package tests", - "open_source": True, - "project_url": "test_url", - "license": "MIT", - "use_poetry": True, - }, - "license": dict( - license="MIT", - ), - "package": dict( - disable=False, - project_name="${globals.project_name}", - description="${globals.description}", - default_branch="${globals.default_branch}", - project_url="${globals.project_url}", - owner="${globals.owner}", - author="${globals.author}", - email="${globals.email}", - license="${license.license}", - python_versions=["3.6", "3.7", "3.8", "3.9"], - pyproject_extra="", - license_classifier="???", - ), - "lint": dict( - project_name="${globals.project_name}", - ), - } -) - -fixture_ids = ["package-conf-cmd", "package-conf-globals"] -example_files = { - Path() / pyproject_toml.dst: pyproject_toml, - Path() / setup_py.dst: setup_py, -} - - -@pytest.fixture(params=[(package_conf, package_conf_with_globals)]) -def config_paths(request): - c1, c2 = request.param - temp_path = tempfile.TemporaryDirectory() - conf_1 = DictConfig(c1) - conf_2 = DictConfig(c2) - filepath_1 = Path(temp_path.name) / "mloq1.yaml" - filepath_2 = Path(temp_path.name) / "mloq2.yaml" - with open(filepath_1, "w") as f: - OmegaConf.save(conf_1, f) - with open(filepath_2, "w") as f: - OmegaConf.save(conf_2, f) - yield filepath_1, filepath_2 - temp_path.cleanup() - - -@pytest.fixture( - params=[ - (PackageCMD, package_conf), - (PackageCMD, package_conf_with_globals), - ], - ids=fixture_ids, - scope="function", -) -def command_and_config(request): - command_cls, conf_dict = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, config - - -@pytest.fixture( - params=[ - (PackageCMD, package_conf, example_files), - (PackageCMD, package_conf_with_globals, example_files), - ], - ids=fixture_ids, - scope="function", -) -def command_and_example(request): - command_cls, conf_dict, example = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, example - - -class TestPackage: - def test_name_is_correct(self, command_and_config): - command, config = command_and_config - assert command.cmd_name == "package" - - def test_equivalent_configs(self, config_paths): - path_conf_1, path_conf_2 = config_paths - temp_path = tempfile.TemporaryDirectory() - temp_path1 = Path(temp_path.name) / "target1" - temp_path2 = Path(temp_path.name) / "target2" - os.makedirs(temp_path1) - os.makedirs(temp_path2) - _run_cmd = run_command(cmd_cls=PackageCMD, use_click=False) - _run_cmd( - config_file=path_conf_1, - output_directory=temp_path1, - overwrite=False, - only_config=False, - interactive=False, - hydra_args="", - ) - _run_cmd( - config_file=path_conf_2, - output_directory=temp_path2, - overwrite=False, - only_config=False, - interactive=False, - hydra_args="", - ) - assert dir_trees_are_equal(str(temp_path1), str(temp_path2)) - temp_path.cleanup() diff --git a/tests/commands/test_project.py b/tests/commands/test_project.py deleted file mode 100644 index 3ccbd68a..00000000 --- a/tests/commands/test_project.py +++ /dev/null @@ -1,122 +0,0 @@ -from pathlib import Path - -from omegaconf import DictConfig -import pytest - -from mloq.commands.project import ( - code_of_conduct, - codecov, - contributing, - gitignore, - init, - main, - makefile, - pre_commit_hook, - ProjectCMD, - readme, - test_main, - test_req, - version, -) -from mloq.files import File -from mloq.writer import CMDRecord -from tests.test_command import TestCommand - - -project_conf = { - "project": dict( - disable=False, - project_name="test_project", - description="test description", - owner="test_owner", - license="MIT", - ), -} - -project_conf_with_globals = DictConfig( - { - "globals": { - "project_name": "test project", - "default_branch": "test_branch", - "owner": "test_owner", - "author": "test_author", - "email": "test_email", - "description": "test_description", - "open_source": True, - "project_url": "???", - }, - "license": {"copyright_holder": "test_holder", "copyright_year": 1990, "license": "MIT"}, - "project": dict( - disable=False, - project_name="${globals.project_name}", - description="${globals.description}", - owner="${globals.owner}", - license="${license.license}", - ), - }, -) -fixture_ids = ["project-conf-cmd", "project-conf-globals"] - - -@pytest.fixture( - params=[(ProjectCMD, project_conf), (ProjectCMD, project_conf_with_globals)], - ids=fixture_ids, - scope="function", -) -def command_and_config(request): - command_cls, conf_dict = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, config - - -def example_files(): - project_path = Path("src") / "test_project" - module_desc = "Python package header for the project module" - test_desc = "Python package header for the test module" - module_init = File( - name=init.name, - src=init.src, - dst=init.dst, - is_static=init.is_static, - description=module_desc, - ) - test_init = File( - name=init.name, - src=init.src, - dst=init.dst, - is_static=init.is_static, - description=test_desc, - ) - example = { - Path() / makefile.dst: makefile, - Path() / readme.dst: readme, - Path() / test_req.dst: test_req, - Path() / pre_commit_hook.dst: pre_commit_hook, - Path() / codecov.dst: codecov, - Path() / gitignore.dst: gitignore, - Path() / code_of_conduct.dst: code_of_conduct, - Path() / contributing.dst: contributing, - project_path / version.dst: version, - project_path / init.dst: module_init, - project_path / main.dst: main, - Path("tests") / test_main.dst: test_main, - Path("tests") / test_init.dst: test_init, - } - return example - - -cmd_examples_param = [ - (ProjectCMD, project_conf, example_files()), - (ProjectCMD, project_conf_with_globals, example_files()), -] - - -@pytest.fixture(params=cmd_examples_param, scope="function", ids=fixture_ids) -def command_and_example(request): - command_cls, conf_dict, example = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, example diff --git a/tests/commands/test_setup.py b/tests/commands/test_setup.py deleted file mode 100644 index 7ea1d484..00000000 --- a/tests/commands/test_setup.py +++ /dev/null @@ -1,86 +0,0 @@ -from pathlib import Path - -from omegaconf import DictConfig, OmegaConf -import pytest - -from mloq.commands.setup import SetupCMD -from mloq.files import mloq_yml -from mloq.writer import CMDRecord -from tests.test_command import command_and_example, TestCommand - - -@pytest.fixture(scope="function") -def command_and_config(): - config = OmegaConf.load(Path(__file__).parent.parent / "examples" / mloq_yml.dst) - record = CMDRecord(config) - command = SetupCMD(record=record) - return command, config - - -class __TestSetupCMD: - def test_class_attributes(self, command_and_config): - command, config = command_and_config - assert isinstance(command.cmd_name, str) - assert isinstance(command.files, tuple) - assert isinstance(command.config, DictConfig) - assert isinstance(command.directories, tuple) - if len(command.directories) > 0: - for directory in command.directories: - assert isinstance(directory, Path) - - def test_parse_config(self, command_and_config): - setup_command, config = command_and_config - setup_command.parse_config() - for command in setup_command.sub_commands: - for key in command.config.keys(): - conf_record = getattr(setup_command.record.config, command.cmd_name) - assert conf_record[key] == config[command.cmd_name][key] - - def test_files_present_in_record(self, command_and_config): - command, config = command_and_config - command.record_files() - file_names = [f.name for f in command.files] - file_srcs = [f.src for f in command.files] - file_dsts = [f.dst for f in command.files] - for f in command.record.files.values(): - assert f.name in file_names - assert f.src in file_srcs - assert f.dst in file_dsts - - def test_record_directories(self, command_and_config): - setup_command, config = command_and_config - setup_command.record_directories() - for command in setup_command.sub_commands: - for directory in command.directories: - assert directory in setup_command.record.directories - for directory in setup_command.record.directories: - assert directory in command.directories - - def __test_run(self, command_and_config): - command, config = command_and_config - record = command.run() - for directory in command.directories: - assert directory in record.directories - - file_names = [f.name for f in command.files] - file_srcs = [f.src for f in command.files] - file_dsts = [f.dst for f in command.files] - for f in record.files.values(): - assert f.name in file_names - assert f.src in file_srcs - assert f.dst in file_dsts - - def __test_files_have_correct_path(self, command_and_example): - if not command_and_example: - return - command, example_files = command_and_example - if example_files is None: - return - record = command.run() - for path, file in record.files.items(): - assert path in example_files - assert example_files[path] == file - - for path, file in example_files.items(): - assert path in record.files - assert record.files[path] == file diff --git a/tests/config/__init__.py b/tests/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/config/fixtures.py b/tests/config/fixtures.py deleted file mode 100644 index f5b1f138..00000000 --- a/tests/config/fixtures.py +++ /dev/null @@ -1,189 +0,0 @@ -import omegaconf -import param -import pytest - -from mloq.config.configuration import Configurable - - -class ConfigurableTest(Configurable): - number = param.Number() - integer = param.Integer() - boolean = param.Boolean() - string = param.String() - list_ = param.List() - dict_ = param.Dict() - tuple_ = param.Tuple(default=(0, "miau", 3)) - - -defaults = { - "number": 16.02, - "integer": 160290, - "boolean": True, - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), -} - -defaults_with_config = { - "number": 16.02, - "integer": 160290, - "boolean": True, - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), - "config": omegaconf.DictConfig( - { - "number": 16.02, - "integer": 160290, - "boolean": True, - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), - }, - ), -} - -only_config = { - "config": omegaconf.DictConfig( - { - "number": 16.02, - "integer": 160290, - "boolean": True, - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), - }, - ), -} - -interp_no_conf = { - "number": 160290.0, - "integer": "${number}", - "boolean": True, - "list_": [0, 1, 2], - "dict_": {}, - "tuple_": (0, "miau", 3), -} - -interp_only_conf = { - "config": omegaconf.DictConfig( - { - "number": 160290.0, - "integer": "${number}", - "boolean": True, - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), - }, - ), -} - -interpolated_both = { - "number": 16.0, - "integer": "${number}", - "boolean": True, - "list_": [0, 1, 2], - "dict_": None, - "tuple_": (0, "miau", 3), - "config": omegaconf.DictConfig( - { - "number": 160290.0, - "integer": "${number}", - "boolean": True, - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), - }, - ), -} - -missing_no_conf = { - "number": "???", - "integer": "${number}", - "boolean": True, - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), -} - -missing_only_conf = { - "config": omegaconf.DictConfig( - { - "number": 160290.0, - "integer": "${number}", - "boolean": "???", - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), - }, - ), -} - -missing_both_different = { - "number": "???", - "integer": "${number}", - "boolean": True, - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), - "config": omegaconf.DictConfig( - { - "number": 160290.0, - "integer": "${number}", - "boolean": True, - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": "???", - }, - ), -} -missing_both_same = { - "number": 16.0, - "integer": "${number}", - "boolean": "???", - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), - "config": omegaconf.DictConfig( - { - "number": 160290.0, - "integer": "${number}", - "boolean": "???", - "list_": [0, 1, 2], - "dict_": {"hola": "adios", "eleven": 11}, - "tuple_": (0, "miau", 3), - }, - ), -} - -basic_params = [{}, defaults, defaults_with_config] -basic_params_id = ["empty-dict", "defaults", "defaults-with-config"] - -missing_params = [missing_no_conf, missing_only_conf, missing_both_same, missing_both_different] -missing_params_ids = [ - "missing-no-conf", - "missing-only-conf", - "missing-both-same", - "missing-both-different", -] - -interpolated_params = [interp_no_conf, interp_only_conf, interpolated_both] -interpolated_params_ids = ["interp-no-conf", "interp-only-conf", "interp-both"] - -configurable_params = basic_params + interpolated_params + missing_params -configurable_params_ids = basic_params_id + interpolated_params_ids + missing_params_ids - - -@pytest.fixture(scope="function", params=tuple(configurable_params), ids=configurable_params_ids) -def configurable(request): - return ConfigurableTest(**request.param) - - -@pytest.fixture(scope="function", params=tuple(interpolated_params), ids=interpolated_params_ids) -def interpolated(request): - return ConfigurableTest(**request.param) - - -@pytest.fixture(scope="function", params=tuple(missing_params), ids=missing_params_ids) -def missing(request): - return ConfigurableTest(**request.param) diff --git a/tests/config/test_configuration.py b/tests/config/test_configuration.py deleted file mode 100644 index cf72e76c..00000000 --- a/tests/config/test_configuration.py +++ /dev/null @@ -1,178 +0,0 @@ -from itertools import product - -import omegaconf -from omegaconf import MISSING, OmegaConf -import param -import pytest - -from mloq.config.configuration import Config, DictConfig, is_interpolation -from tests.config.fixtures import configurable, ConfigurableTest, interpolated, interpolated_params - - -def interpolation_is_consistent( - configurable_, - key, -): - c = configurable_.conf - itps = c.interpolations - unresolved = OmegaConf.to_container(configurable_.config, resolve=False) - assert key in unresolved - assert len(itps) > 0 - assert key in itps - assert c.is_interpolation(key) - raw_value = unresolved[key] - param_value = getattr(configurable_, key) - assert isinstance(raw_value, str) - assert raw_value != param_value - if not isinstance(getattr(configurable_.param, key), param.String): - assert not isinstance(param_value, str) - return True - - -def missing_is_consistent(configurable, key, is_missing: False) -> bool: - c = configurable.config - is_miss = OmegaConf.is_missing(c, key) - assert is_miss and is_missing - if not is_miss: - return True - - with pytest.raises(omegaconf.errors.MissingMandatoryValue): - OmegaConf.to_container(c, throw_on_missing=True) - - -class TestConfig: - def test_init(self, configurable): - pass - - def test_attributes(self, configurable): - c = configurable.conf - assert isinstance(c.config, omegaconf.DictConfig) - interp = c.interpolations - assert isinstance(interp, dict) - for k, v in interp.items(): - assert isinstance(k, str) - assert isinstance(v, str) - assert is_interpolation(v) - - for x in c.missing: - assert isinstance(x, str) - assert x in configurable.config.keys() - if not omegaconf.OmegaConf.is_interpolation(configurable.config, x): - assert omegaconf.OmegaConf.is_missing(configurable.config, x) - - @pytest.mark.parametrize("key, inplace", product([None, "integer", "number"], [True, False])) - def test_resolve_does_not_crash(self, configurable, inplace, key): - configurable.conf.resolve(key=key, inplace=inplace) - - def test_resolve_inplace_not_key(self, configurable): - interps = configurable.conf.interpolations - - ret = configurable.conf.resolve(inplace=True) - assert ret is None - assert len(configurable.conf.interpolations) == 0 - cont = OmegaConf.to_container(configurable.config, resolve=False) - interps_keys = [ - k for k in cont.keys() if OmegaConf.is_interpolation(configurable.config, k) - ] - assert len(interps_keys) == 0 - if len(interps): - for k in cont.keys(): - assert not OmegaConf.is_interpolation(configurable.config, k) - - def test_resolve_inplace_key(self, configurable): - ret = configurable.conf.resolve(key="integer", inplace=True) - assert ret is None - assert len(configurable.conf.interpolations) == 0 - cont = OmegaConf.to_container(configurable.config, resolve=False) - interps = { - k: v - for k, v in cont.items() - if OmegaConf.is_interpolation(configurable.config, str(k)) - } - assert len(interps) == 0 - - def test_resolve_not_inplace_not_key(self, configurable): - interps = configurable.conf.interpolations - ret = configurable.conf.resolve(inplace=False) - assert isinstance(ret, dict) - assert all([hasattr(configurable, k) for k in ret.keys()]) - assert len(configurable.conf.interpolations) == len(interps) - cont = OmegaConf.to_container(configurable.config, resolve=False) - interps_keys = [ - k for k in cont.keys() if OmegaConf.is_interpolation(configurable.config, k) - ] - assert len(interps_keys) == len(interps) - - def test_resolve_not_inplace_key(self, configurable): - interps = configurable.conf.interpolations - ret = configurable.conf.resolve(key="integer", inplace=False) - assert not isinstance(ret, str) - assert not isinstance(ret, float) - assert hasattr(configurable, "integer") - assert isinstance(configurable.integer, int) - assert len(configurable.conf.interpolations) == len(interps) - cont = OmegaConf.to_container(configurable.config, resolve=False) - interps_keys = [ - k for k in cont.keys() if OmegaConf.is_interpolation(configurable.config, k) - ] - assert len(interps_keys) == len(interps) - - def test_to_param_type(self, configurable): - interp_0 = configurable.conf.interpolations - for k in configurable.config.keys(): - param_val = getattr(configurable, k) - to_ptype_self = configurable.conf.to_param_type(k) - assert param_val == to_ptype_self, ( - f"key: {k}, conf: {configurable.config} interps {configurable.conf.interpolations}" - f" number {configurable.number}" - ) - - if configurable.conf.is_interpolation(k): - unresolved = OmegaConf.to_container(configurable.config, resolve=False) - interp_1 = configurable.conf.interpolations - assert interp_1 == interp_0 - assert len(interp_1) > 0 - assert k in interp_0 - assert k in interp_1 - assert interp_1[k] != param_val - assert interp_1[k] != param_val - assert isinstance(unresolved[k], str) - if not isinstance(configurable.conf.params[k], param.String): - assert not isinstance(param_val, str) - - def __test_update_value(self, configurable): - c = configurable.conf - if "integer" in c.config: - configurable.integer = 777 - configurable.config.integer = 777 - configurable.number = 42.0 - configurable.config.number = 42.0 - c.update_value("integer", "${number}") - assert configurable.integer == int(configurable.number) - assert isinstance(configurable.integer, int) and isinstance(configurable.number, float) - assert "integer" in c.interpolations - - def test_sync(self, interpolated): - interps = interpolated.conf.interpolations - assert len(interps) > 0, f"conf {interpolated.config} interps: {interps}" - interpolated.conf.sync() - for k in interps: - assert interpolation_is_consistent(interpolated, k) - assert len(interpolated.conf.interpolations) > 0 - - -class TestConfigurable: - def test_conf(self, configurable): - assert hasattr(configurable, "conf") - assert hasattr(configurable, "_Configurable__conf") - assert configurable.conf == configurable._Configurable__conf - assert isinstance(configurable.conf, Config) - with pytest.raises(AttributeError): - configurable.conf = "fails" - - def test_config(self, configurable): - assert isinstance(configurable.config, omegaconf.DictConfig) - assert isinstance(configurable.param.config, DictConfig) - assert not configurable.param.config.readonly - assert configurable.param.config.per_instance - assert configurable.param.config.instantiate diff --git a/tests/config/test_param_patch.py b/tests/config/test_param_patch.py deleted file mode 100644 index 7a63fa22..00000000 --- a/tests/config/test_param_patch.py +++ /dev/null @@ -1,40 +0,0 @@ -from omegaconf import MISSING -import param as param_ -import pytest - -from mloq.config.param_patch import param - - -patched_classes = [getattr(param, p) for p in sorted(param.PATCHED_PARAMETERS)] - - -@pytest.fixture(scope="module", params=patched_classes) -def patched_class(request): - return request.param - - -class TestParamPatch: - def test_init_default(self, patched_class): - instance = patched_class() - param_class = getattr(param_, patched_class.__name__) - assert isinstance(instance, param_class) - - def test_init_missing(self, patched_class): - - instance = ( - patched_class(MISSING, length=3) - if patched_class.__name__ == "Tuple" - else patched_class(MISSING) - ) - assert instance.default is None - assert instance._missing_init - - def test_init_interpolation(self, patched_class): - - instance = ( - patched_class("${global}", length=3) - if patched_class.__name__ == "Tuple" - else patched_class("${global}") - ) - assert instance.default is None - assert instance._interpolation_init diff --git a/tests/examples/__init__.py b/tests/examples/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/examples/ci/test_1/mloq.yaml b/tests/examples/ci/test_1/mloq.yaml deleted file mode 100644 index 7c6ebdeb..00000000 --- a/tests/examples/ci/test_1/mloq.yaml +++ /dev/null @@ -1,94 +0,0 @@ -globals: - project_name: test_docs - default_branch: master - owner: tester - author: tester-docs - email: test_docs@mail.com - description: test docs command - open_source: true - project_url: "http://example_url/docs_test" - -license: - disable: false - license: MIT - copyright_year: ${current_year:} - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - -project: - disable: false - docker: false # FIXME: depends on docker command - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: false - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - requirements: - - None - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - -lint: - disable: false - docstring_checks: true - pyproject_extra: "" - project_name: ${globals.project_name} - -docker: - disable: false - docker_image: fragiletech/ubuntu20.04-base-py38 - docker_org: ${globals.owner} - -ci: - bot_name: my_bot - bot_email: my_bot_email - python_versions: ${package.python_versions} - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - ci_extra: "" - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: my_org - docker: true diff --git a/tests/examples/ci/test_1/target/.github/workflows/push.yml b/tests/examples/ci/test_1/target/.github/workflows/push.yml deleted file mode 100644 index d579ab91..00000000 --- a/tests/examples/ci/test_1/target/.github/workflows/push.yml +++ /dev/null @@ -1,251 +0,0 @@ -name: Push - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - PROJECT_NAME: test_docs - PROJECT_DIR: test_docs - VERSION_FILE: test_docs/version.py - DEFAULT_BRANCH: master - BOT_NAME: my_bot - BOT_EMAIL: my_bot_email - DOCKER_ORG: my_org - PIP_CACHE: | - ~/.cache/pip - ~/.local/bin - ~/.local/lib/python3.*/site-packages - -jobs: - style-check: - name: Style check - if: "!contains(github.event.head_commit.message, 'Bump version')" - runs-on: ubuntu-20.04 - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: "3.8" - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-lint-${{ hashFiles('requirements-lint.txt') }} - restore-keys: ubuntu-20.04-pip-lint- - - name: Install lint dependencies - run: | - set -x - pip install -r requirements-lint.txt - - - name: Run style check and linter - run: | - set -x - make check - - pytest: - name: Run Pytest - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - strategy: - matrix: - python-version: ['3.6', '3.7', '3.8', '3.9'] - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-test-${{ matrix.python-version }}-${{ hashFiles('requirements.txt', 'requirements-test.txt') }} - restore-keys: ubuntu-20.04-pip-test- - - name: Install test and package dependencies - run: | - set -x - pip install -r requirements-test.txt -r requirements.txt - pip install . - - - name: Test with pytest - run: | - set -x - make test-codecov - - - name: Upload coverage report - if: ${{ matrix.python-version=='3.8' }} - uses: codecov/codecov-action@v1 - - test-docker: - name: Test Docker container - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - uses: actions/checkout@v2 - - name: Build container - run: | - set -x - make docker-build - - name: Run tests - run: | - set -x - make docker-test - - build-test-package: - name: Build and test the package - needs: style-check - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-test-3.8-${{ hashFiles('requirements.txt', 'requirements-test.txt') }} - restore-keys: ubuntu-20.04-pip-test- - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel bump2version - - - name: Create unique version for test.pypi - run: | - set -x - current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2) - ts=$(date +%s) - new_version="$current_version$ts" - bumpversion --current-version $current_version --new-version $new_version patch $VERSION_FILE - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to TestPyPI - env: - TEST_PYPI_PASS: ${{ secrets.TEST_PYPI_PASS }} - if: "'$TEST_PYPI_PASS' != ''" - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.TEST_PYPI_PASS }} - repository_url: https://test.pypi.org/legacy/ - skip_existing: true - - - name: Install dependencies - run: | - set -x - python -m pip install dist/*.whl -r requirements-test.txt - - - name: Test package - run: | - set -x - rm -rf $PROJECT_DIR - make test - - bump-version: - name: Bump package version - env: - BOT_AUTH_TOKEN: ${{ secrets.BOT_AUTH_TOKEN }} - if: "!contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$BOT_AUTH_TOKEN' != ''" - runs-on: ubuntu-20.04 - needs: - - pytest - - build-test-package - - test-docker - steps: - - name: actions/checkout - uses: actions/checkout@v2 - with: - persist-credentials: false - fetch-depth: 100 - - name: current_version - run: | - set -x - echo "current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2)" >> $GITHUB_ENV - echo "version_file=$VERSION_FILE" >> $GITHUB_ENV - echo 'bot_name="${BOT_NAME}"' >> $GITHUB_ENV - echo 'bot_email="${BOT_EMAIL}"' >> $GITHUB_ENV - - name: FragileTech/bump-version - uses: FragileTech/bump-version@main - with: - current_version: "${{ env.current_version }}" - files: "${{ env.version_file }}" - commit_name: "${{ env.bot_name }}" - commit_email: "${{ env.bot_email }}" - login: "${{ env.bot_name }}" - token: "${{ secrets.BOT_AUTH_TOKEN }}" - - push-docker: - name: Push Docker container - runs-on: ubuntu-20.04 - env: - DOCKERHUB_PASS: ${{ secrets.DOCKERHUB_PASS }} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$DOCKERHUB_PASS' != ''" - steps: - - uses: actions/checkout@v2 - - name: Login to DockerHub - run: | - set -x - docker login -u "${{ secrets.DOCKERHUB_LOGIN }}" -p "${{ secrets.DOCKERHUB_PASS }}" docker.io - - - name: Build container - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-build VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG - - name: Push images - - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-push VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG - - release-package: - name: Release PyPI package - env: - PYPI_PASS: ${{ secrets.PYPI_PASS }} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$PYPI_PASS' != ''" - runs-on: ubuntu-20.04 - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.PYPI_PASS }} diff --git a/tests/examples/ci/test_1/target/WHAT_MLOQ_GENERATED.md b/tests/examples/ci/test_1/target/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index d670399e..00000000 --- a/tests/examples/ci/test_1/target/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,3 +0,0 @@ -# What mloq generated for your project - -* `push.yml` - GitHub Actions continuous integration workflow file diff --git a/tests/examples/ci/test_mloq/mloq.yaml b/tests/examples/ci/test_mloq/mloq.yaml deleted file mode 100644 index 05f7e7ba..00000000 --- a/tests/examples/ci/test_mloq/mloq.yaml +++ /dev/null @@ -1,141 +0,0 @@ -globals: - project_name: mloq - default_branch: master - owner: FragileTech - author: FragileTech - email: guillem@fragile.tech - description: Package for initializing ML projects following ML Ops best practices. - open_source: true - project_url: https://github.com/FragileTech/ml-ops-quickstart - -license: - disable: false - license: MIT - copyright_year: 2020 - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - project_name: ${globals.project_name} - email: ${globals.email} - project_url: ${globals.project_url} - -project: - disable: false - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - project_url: ${globals.project_url} - tests: true - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - license_classifier: "License :: OSI Approved :: MIT License" - pipenv: true - -requirements: - disable: false - requirements: - - dogfood - -lint: - disable: false - docstring_checks: true - pyproject_extra: |- - [tool.flakehell.exceptions."**/assets/*"] - pycodestyle = ["-*"] - pyflakes = ["-*"] - "flake8*" = ["-*"] - project_name: ${globals.project_name} - makefile: true - -docker: - disable: false - cuda: ??? - cuda_image_type: "cudnn8-runtime-ubuntu20.04" - cuda_version: "11.2" - python_version: "3.8" - ubuntu_version: "20.04" - base_image: "???" - test: true - lint: true - jupyter: true - jupyter_password: ${globals.project_name} - project_name: ${globals.project_name} - docker_org: fragiletech #${globals.owner} - requirements: ${requirements} - extra: "" - makefile: true - -ci: - bot_name: fragile-bot - bot_email: bot@fragile.tech - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: fragiletech - docker: true - python_versions: ${package.python_versions} - ci_extra: |- - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py \ No newline at end of file diff --git a/tests/examples/ci/test_mloq/target/.github/workflows/push.yml b/tests/examples/ci/test_mloq/target/.github/workflows/push.yml deleted file mode 100644 index e6e45358..00000000 --- a/tests/examples/ci/test_mloq/target/.github/workflows/push.yml +++ /dev/null @@ -1,299 +0,0 @@ -name: Push - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - PROJECT_NAME: mloq - PROJECT_DIR: mloq - VERSION_FILE: mloq/version.py - DEFAULT_BRANCH: master - BOT_NAME: fragile-bot - BOT_EMAIL: bot@fragile.tech - DOCKER_ORG: fragiletech - PIP_CACHE: | - ~/.cache/pip - ~/.local/bin - ~/.local/lib/python3.*/site-packages - -jobs: - style-check: - name: Style check - if: "!contains(github.event.head_commit.message, 'Bump version')" - runs-on: ubuntu-20.04 - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: "3.8" - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-lint-${{ hashFiles('requirements-lint.txt') }} - restore-keys: ubuntu-20.04-pip-lint- - - name: Install lint dependencies - run: | - set -x - pip install -r requirements-lint.txt - - - name: Run style check and linter - run: | - set -x - make check - - pytest: - name: Run Pytest - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - strategy: - matrix: - python-version: ['3.6', '3.7', '3.8', '3.9'] - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-test-${{ matrix.python-version }}-${{ hashFiles('requirements.txt', 'requirements-test.txt') }} - restore-keys: ubuntu-20.04-pip-test- - - name: Install test and package dependencies - run: | - set -x - pip install -r requirements-test.txt -r requirements.txt - pip install . - - name: Additional setup - run: | - set -x - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py - - - name: Test with pytest - run: | - set -x - make test-codecov - - - name: Upload coverage report - if: ${{ matrix.python-version=='3.8' }} - uses: codecov/codecov-action@v1 - - test-docker: - name: Test Docker container - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - uses: actions/checkout@v2 - - name: Build container - run: | - set -x - make docker-build - - name: Run tests - run: | - set -x - make docker-test - - build-test-package: - name: Build and test the package - needs: style-check - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-test-3.8-${{ hashFiles('requirements.txt', 'requirements-test.txt') }} - restore-keys: ubuntu-20.04-pip-test- - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel bump2version - - - name: Create unique version for test.pypi - run: | - set -x - current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2) - ts=$(date +%s) - new_version="$current_version$ts" - bumpversion --current-version $current_version --new-version $new_version patch $VERSION_FILE - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to TestPyPI - env: - TEST_PYPI_PASS: ${{ secrets.TEST_PYPI_PASS }} - if: "'$TEST_PYPI_PASS' != ''" - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.TEST_PYPI_PASS }} - repository_url: https://test.pypi.org/legacy/ - skip_existing: true - - - name: Install dependencies - run: | - set -x - python -m pip install dist/*.whl -r requirements-test.txt - - name: Additional setup - run: | - set -x - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py - - - name: Test package - run: | - set -x - rm -rf $PROJECT_DIR - make test - - bump-version: - name: Bump package version - env: - BOT_AUTH_TOKEN: ${{ secrets.BOT_AUTH_TOKEN }} - if: "!contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$BOT_AUTH_TOKEN' != ''" - runs-on: ubuntu-20.04 - needs: - - pytest - - build-test-package - - test-docker - steps: - - name: actions/checkout - uses: actions/checkout@v2 - with: - persist-credentials: false - fetch-depth: 100 - - name: current_version - run: | - set -x - echo "current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2)" >> $GITHUB_ENV - echo "version_file=$VERSION_FILE" >> $GITHUB_ENV - echo 'bot_name="${BOT_NAME}"' >> $GITHUB_ENV - echo 'bot_email="${BOT_EMAIL}"' >> $GITHUB_ENV - - name: FragileTech/bump-version - uses: FragileTech/bump-version@main - with: - current_version: "${{ env.current_version }}" - files: "${{ env.version_file }}" - commit_name: "${{ env.bot_name }}" - commit_email: "${{ env.bot_email }}" - login: "${{ env.bot_name }}" - token: "${{ secrets.BOT_AUTH_TOKEN }}" - - push-docker: - name: Push Docker container - runs-on: ubuntu-20.04 - env: - DOCKERHUB_PASS: ${{ secrets.DOCKERHUB_PASS }} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$DOCKERHUB_PASS' != ''" - steps: - - uses: actions/checkout@v2 - - name: Login to DockerHub - run: | - set -x - docker login -u "${{ secrets.DOCKERHUB_LOGIN }}" -p "${{ secrets.DOCKERHUB_PASS }}" docker.io - - - name: Build container - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-build VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG - - name: Push images - - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-push VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG - - release-package: - name: Release PyPI package - env: - PYPI_PASS: ${{ secrets.PYPI_PASS }} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$PYPI_PASS' != ''" - runs-on: ubuntu-20.04 - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.PYPI_PASS }} diff --git a/tests/examples/ci/test_mloq/target/WHAT_MLOQ_GENERATED.md b/tests/examples/ci/test_mloq/target/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index d670399e..00000000 --- a/tests/examples/ci/test_mloq/target/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,3 +0,0 @@ -# What mloq generated for your project - -* `push.yml` - GitHub Actions continuous integration workflow file diff --git a/tests/examples/docker/test_1/mloq.yaml b/tests/examples/docker/test_1/mloq.yaml deleted file mode 100644 index ec284677..00000000 --- a/tests/examples/docker/test_1/mloq.yaml +++ /dev/null @@ -1,71 +0,0 @@ -globals: - project_name: dockerproject - default_branch: master - owner: dockerorg - author: ??? - email: ??? - description: ??? - open_source: ??? - project_url: ??? - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - license_classifier: "" - -requirements: - disable: false - requirements: - - None - - -docker: - disable: false - cuda: ??? - cuda_image_type: "cudnn8-runtime-ubuntu20.04" - cuda_version: "11.2" - python_version: "3.8" - ubuntu_version: "20.04" - base_image: "???" - test: true - lint: true - jupyter: true - jupyter_password: ${globals.project_name} - project_name: ${globals.project_name} - docker_org: fragiletech #${globals.owner} - requirements: ${requirements.requirements} - extra: "" - makefile: true - -ci: - disable: false - bot_name: ??? - bot_email: ??? - docker: ??? - vendor: github - python_versions: ${package.python_versions} - ubuntu_version: ubuntu-18.04 - ci_python_version: '3.8' - ci_extra: "" - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: ${docker.docker_org} \ No newline at end of file diff --git a/tests/examples/docker/test_1/target/Dockerfile b/tests/examples/docker/test_1/target/Dockerfile deleted file mode 100644 index 06631fc1..00000000 --- a/tests/examples/docker/test_1/target/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM ubuntu:20.04 -ARG JUPYTER_PASSWORD="dockerproject" -ENV BROWSER=/browser \ - LC_ALL=en_US.UTF-8 \ - LANG=en_US.UTF-8 -COPY Makefile.docker Makefile -COPY . dockerproject/ - -RUN apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends make cmake && \ - make install-python3.8 && \ - make install-common-dependencies && \ - make install-python-libs - -RUN cd dockerproject \ - && python3 -m pip install -U pip \ - && pip3 install -r requirements-lint.txt \ - && pip3 install -r requirements-test.txt \ - && pip3 install -r requirements.txt \ - && pip install ipython jupyter \ - && pip3 install -e . \ - && git config --global init.defaultBranch master \ - && git config --global user.name "Whoever" \ - && git config --global user.email "whoever@fragile.tech" - -RUN make remove-dev-packages - -RUN mkdir /root/.jupyter && \ - echo 'c.NotebookApp.token = "'${JUPYTER_PASSWORD}'"' > /root/.jupyter/jupyter_notebook_config.py -CMD jupyter notebook --allow-root --port 8080 --ip 0.0.0.0 diff --git a/tests/examples/docker/test_1/target/Makefile.docker b/tests/examples/docker/test_1/target/Makefile.docker deleted file mode 100644 index 67b30b5a..00000000 --- a/tests/examples/docker/test_1/target/Makefile.docker +++ /dev/null @@ -1,79 +0,0 @@ -current_dir = $(shell pwd) - -PROJECT = dockerfiles -VERSION ?= latest -DOCKER_TAG = None -PYTHON_VERSION = "3.8" -UBUNTU_NAME = $(lsb_release -s -c) - -# Install system packages -.PHONY: install-common-dependencies -install-common-dependencies: - apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends \ - ca-certificates locales pkg-config apt-utils gcc g++ wget make cmake git curl flex ssh gpgv \ - libffi-dev libjpeg-turbo-progs libjpeg8-dev libjpeg-turbo8 libjpeg-turbo8-dev gnupg2 \ - libpng-dev libpng16-16 libglib2.0-0 bison gfortran lsb-release \ - libsm6 libxext6 libxrender1 libfontconfig1 libhdf5-dev libopenblas-base libopenblas-dev \ - libfreetype6 libfreetype6-dev zlib1g-dev zlib1g xvfb python-opengl ffmpeg libhdf5-dev && \ - ln -s /usr/lib/x86_64-linux-gnu/libz.so /lib/ && \ - ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /lib/ && \ - echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ - locale-gen && \ - wget -O - https://bootstrap.pypa.io/get-pip.py | python3 && \ - rm -rf /var/lib/apt/lists/* && \ - echo '#!/bin/bash\n\\n\echo\n\echo " $@"\n\echo\n\' > /browser && \ - chmod +x /browser - - -.PHONY: remove-dev-packages -remove-dev-packages: - pip3 uninstall -y cython && \ - apt-get remove -y cmake pkg-config flex bison curl libpng-dev \ - libjpeg-turbo8-dev zlib1g-dev libhdf5-dev libopenblas-dev gfortran \ - libfreetype6-dev libjpeg8-dev libffi-dev && \ - apt-get autoremove -y && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Install Python 3.9 -.PHONY: install-python3.9 -install-python3.9: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.9 python3.9-dev python3-distutils python3-setuptools - -# Install Python 3.8 -.PHONY: install-python3.8 -install-python3.8: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.8 python3.8-dev python3-distutils python3-setuptools - -# Install Python 3.7 -.PHONY: install-python3.7 -install-python3.7: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.7 python3.7-dev python3-distutils python3-setuptools - -# Install Python 3.6 -.PHONY: install-python3.6 -install-python3.6: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.6 python3.6-dev python3-distutils python3-setuptools \ - -# Install phantomjs for holoviews image save -.PHONY: install-phantomjs -install-phantomjs: - curl -sSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ - echo "deb https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee /etc/apt/sources.list.d/nodesource.list && \ - echo "deb-src https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee -a /etc/apt/sources.list.d/nodesource.list && \ - apt-get update && apt-get install -y nodejs && \ - npm install phantomjs --unsafe-perm && \ - npm install -g phantomjs-prebuilt --unsafe-perm - -# Install common python dependencies -.PHONY: install-python-libs -install-python-libs: - python3 -m pip install -U pip && \ - pip3 install --no-cache-dir setuptools wheel cython pipenv && \ - pip3 install --no-cache-dir matplotlib && \ - python3 -c "import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot" diff --git a/tests/examples/docker/test_1/target/WHAT_MLOQ_GENERATED.md b/tests/examples/docker/test_1/target/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index cc288b47..00000000 --- a/tests/examples/docker/test_1/target/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,4 +0,0 @@ -# What mloq generated for your project - -* `Dockerfile` - Docker container for the project -* `Makefile.docker` - Makefile for the Docker container setup diff --git a/tests/examples/docker/test_cuda/mloq.yaml b/tests/examples/docker/test_cuda/mloq.yaml deleted file mode 100644 index caecd2b2..00000000 --- a/tests/examples/docker/test_cuda/mloq.yaml +++ /dev/null @@ -1,143 +0,0 @@ -globals: - project_name: mloq - default_branch: master - owner: FragileTech - author: FragileTech - email: guillem@fragile.tech - description: Package for initializing ML projects following ML Ops best practices. - open_source: true - project_url: https://github.com/FragileTech/ml-ops-quickstart - license: "MIT" - use_poetry: true - -license: - disable: false - license: MIT - copyright_year: 2020 - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - project_name: ${globals.project_name} - email: ${globals.email} - project_url: ${globals.project_url} - -project: - disable: false - docker: false # FIXME: depends on docker command - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - project_url: ${globals.project_url} - tests: true - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - python_versions: - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - license_classifier: "License :: OSI Approved :: MIT License" - pipenv: true - -requirements: - disable: false - requirements: - - dogfood - -lint: - disable: false - docstring_checks: true - pyproject_extra: |- - [tool.flakehell.exceptions."**/assets/*"] - pycodestyle = ["-*"] - pyflakes = ["-*"] - "flake8*" = ["-*"] - project_name: ${globals.project_name} - makefile: true - -docker: - disable: false - cuda: True - cuda_image_type: "cudnn8-runtime" - cuda_version: "11.6.0" - python_version: "3.8" - ubuntu_version: "20.04" - base_image: "???" - test: true - lint: true - jupyter: true - jupyter_password: ${globals.project_name} - project_name: ${globals.project_name} - docker_org: fragiletech #${globals.owner} - requirements: ${requirements.requirements} - extra: "" - makefile: true - -ci: - bot_name: fragile-bot - bot_email: bot@fragile.tech - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: fragiletech - docker: true - python_versions: ${package.python_versions} - ci_extra: |- - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py \ No newline at end of file diff --git a/tests/examples/docker/test_cuda/target/Dockerfile b/tests/examples/docker/test_cuda/target/Dockerfile deleted file mode 100644 index f894d6ce..00000000 --- a/tests/examples/docker/test_cuda/target/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM nvidia/cuda:11.2-cudnn8-runtime-ubuntu20.04 -ARG JUPYTER_PASSWORD="mloq" -ENV BROWSER=/browser \ - LC_ALL=en_US.UTF-8 \ - LANG=en_US.UTF-8 -COPY Makefile.docker Makefile -COPY . mloq/ - -RUN apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends make cmake && \ - make install-python3.8 && \ - make install-common-dependencies && \ - make install-python-libs - -RUN cd mloq \ - && python3 -m pip install -U pip \ - && pip3 install -r requirements-lint.txt \ - && pip3 install -r requirements-test.txt \ - && pip3 install -r requirements.txt \ - && pip3 install ipython jupyter \ - && pip3 install -e . \ - && git config --global init.defaultBranch master \ - && git config --global user.name "Whoever" \ - && git config --global user.email "whoever@fragile.tech" - -RUN make remove-dev-packages - -RUN mkdir /root/.jupyter && \ - echo 'c.NotebookApp.token = "'${JUPYTER_PASSWORD}'"' > /root/.jupyter/jupyter_notebook_config.py -CMD jupyter notebook --allow-root --port 8080 --ip 0.0.0.0 diff --git a/tests/examples/docker/test_cuda/target/Makefile.docker b/tests/examples/docker/test_cuda/target/Makefile.docker deleted file mode 100644 index 67b30b5a..00000000 --- a/tests/examples/docker/test_cuda/target/Makefile.docker +++ /dev/null @@ -1,79 +0,0 @@ -current_dir = $(shell pwd) - -PROJECT = dockerfiles -VERSION ?= latest -DOCKER_TAG = None -PYTHON_VERSION = "3.8" -UBUNTU_NAME = $(lsb_release -s -c) - -# Install system packages -.PHONY: install-common-dependencies -install-common-dependencies: - apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends \ - ca-certificates locales pkg-config apt-utils gcc g++ wget make cmake git curl flex ssh gpgv \ - libffi-dev libjpeg-turbo-progs libjpeg8-dev libjpeg-turbo8 libjpeg-turbo8-dev gnupg2 \ - libpng-dev libpng16-16 libglib2.0-0 bison gfortran lsb-release \ - libsm6 libxext6 libxrender1 libfontconfig1 libhdf5-dev libopenblas-base libopenblas-dev \ - libfreetype6 libfreetype6-dev zlib1g-dev zlib1g xvfb python-opengl ffmpeg libhdf5-dev && \ - ln -s /usr/lib/x86_64-linux-gnu/libz.so /lib/ && \ - ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /lib/ && \ - echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ - locale-gen && \ - wget -O - https://bootstrap.pypa.io/get-pip.py | python3 && \ - rm -rf /var/lib/apt/lists/* && \ - echo '#!/bin/bash\n\\n\echo\n\echo " $@"\n\echo\n\' > /browser && \ - chmod +x /browser - - -.PHONY: remove-dev-packages -remove-dev-packages: - pip3 uninstall -y cython && \ - apt-get remove -y cmake pkg-config flex bison curl libpng-dev \ - libjpeg-turbo8-dev zlib1g-dev libhdf5-dev libopenblas-dev gfortran \ - libfreetype6-dev libjpeg8-dev libffi-dev && \ - apt-get autoremove -y && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Install Python 3.9 -.PHONY: install-python3.9 -install-python3.9: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.9 python3.9-dev python3-distutils python3-setuptools - -# Install Python 3.8 -.PHONY: install-python3.8 -install-python3.8: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.8 python3.8-dev python3-distutils python3-setuptools - -# Install Python 3.7 -.PHONY: install-python3.7 -install-python3.7: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.7 python3.7-dev python3-distutils python3-setuptools - -# Install Python 3.6 -.PHONY: install-python3.6 -install-python3.6: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.6 python3.6-dev python3-distutils python3-setuptools \ - -# Install phantomjs for holoviews image save -.PHONY: install-phantomjs -install-phantomjs: - curl -sSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ - echo "deb https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee /etc/apt/sources.list.d/nodesource.list && \ - echo "deb-src https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee -a /etc/apt/sources.list.d/nodesource.list && \ - apt-get update && apt-get install -y nodejs && \ - npm install phantomjs --unsafe-perm && \ - npm install -g phantomjs-prebuilt --unsafe-perm - -# Install common python dependencies -.PHONY: install-python-libs -install-python-libs: - python3 -m pip install -U pip && \ - pip3 install --no-cache-dir setuptools wheel cython pipenv && \ - pip3 install --no-cache-dir matplotlib && \ - python3 -c "import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot" diff --git a/tests/examples/docker/test_cuda/target/WHAT_MLOQ_GENERATED.md b/tests/examples/docker/test_cuda/target/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index cc288b47..00000000 --- a/tests/examples/docker/test_cuda/target/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,4 +0,0 @@ -# What mloq generated for your project - -* `Dockerfile` - Docker container for the project -* `Makefile.docker` - Makefile for the Docker container setup diff --git a/tests/examples/docker/test_mloq/mloq.yaml b/tests/examples/docker/test_mloq/mloq.yaml deleted file mode 100644 index 17ebb7c8..00000000 --- a/tests/examples/docker/test_mloq/mloq.yaml +++ /dev/null @@ -1,142 +0,0 @@ -globals: - project_name: mloq - default_branch: master - owner: FragileTech - author: FragileTech - email: guillem@fragile.tech - description: Package for initializing ML projects following ML Ops best practices. - open_source: true - project_url: https://github.com/FragileTech/ml-ops-quickstart - -license: - disable: false - license: MIT - copyright_year: 2020 - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - project_name: ${globals.project_name} - email: ${globals.email} - project_url: ${globals.project_url} - -project: - disable: false - docker: false # FIXME: depends on docker command - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - project_url: ${globals.project_url} - tests: true - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - license_classifier: "License :: OSI Approved :: MIT License" - pipenv: true - -requirements: - disable: false - requirements: - - dogfood - -lint: - disable: false - docstring_checks: true - pyproject_extra: |- - [tool.flakehell.exceptions."**/assets/*"] - pycodestyle = ["-*"] - pyflakes = ["-*"] - "flake8*" = ["-*"] - project_name: ${globals.project_name} - makefile: true - -docker: - disable: false - cuda: ??? - cuda_image_type: "cudnn8-runtime-ubuntu20.04" - cuda_version: "11.2" - python_version: "3.8" - ubuntu_version: "20.04" - base_image: "???" - test: true - lint: true - jupyter: true - jupyter_password: ${globals.project_name} - project_name: ${globals.project_name} - docker_org: fragiletech #${globals.owner} - requirements: ${requirements.requirements} - extra: "" - makefile: true - -ci: - bot_name: fragile-bot - bot_email: bot@fragile.tech - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: fragiletech - docker: true - python_versions: ${package.python_versions} - ci_extra: |- - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py \ No newline at end of file diff --git a/tests/examples/docker/test_mloq/target/Dockerfile b/tests/examples/docker/test_mloq/target/Dockerfile deleted file mode 100644 index 2b7847ee..00000000 --- a/tests/examples/docker/test_mloq/target/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM ubuntu:20.04 -ARG JUPYTER_PASSWORD="mloq" -ENV BROWSER=/browser \ - LC_ALL=en_US.UTF-8 \ - LANG=en_US.UTF-8 -COPY Makefile.docker Makefile -COPY . mloq/ - -RUN apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends make cmake && \ - make install-python3.8 && \ - make install-common-dependencies && \ - make install-python-libs - -RUN cd mloq \ - && python3 -m pip install -U pip \ - && pip3 install -r requirements-lint.txt \ - && pip3 install -r requirements-test.txt \ - && pip3 install -r requirements.txt \ - && pip install ipython jupyter \ - && pip3 install -e . \ - && git config --global init.defaultBranch master \ - && git config --global user.name "Whoever" \ - && git config --global user.email "whoever@fragile.tech" - -RUN make remove-dev-packages - -RUN mkdir /root/.jupyter && \ - echo 'c.NotebookApp.token = "'${JUPYTER_PASSWORD}'"' > /root/.jupyter/jupyter_notebook_config.py -CMD jupyter notebook --allow-root --port 8080 --ip 0.0.0.0 diff --git a/tests/examples/docker/test_mloq/target/Makefile.docker b/tests/examples/docker/test_mloq/target/Makefile.docker deleted file mode 100644 index 67b30b5a..00000000 --- a/tests/examples/docker/test_mloq/target/Makefile.docker +++ /dev/null @@ -1,79 +0,0 @@ -current_dir = $(shell pwd) - -PROJECT = dockerfiles -VERSION ?= latest -DOCKER_TAG = None -PYTHON_VERSION = "3.8" -UBUNTU_NAME = $(lsb_release -s -c) - -# Install system packages -.PHONY: install-common-dependencies -install-common-dependencies: - apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends \ - ca-certificates locales pkg-config apt-utils gcc g++ wget make cmake git curl flex ssh gpgv \ - libffi-dev libjpeg-turbo-progs libjpeg8-dev libjpeg-turbo8 libjpeg-turbo8-dev gnupg2 \ - libpng-dev libpng16-16 libglib2.0-0 bison gfortran lsb-release \ - libsm6 libxext6 libxrender1 libfontconfig1 libhdf5-dev libopenblas-base libopenblas-dev \ - libfreetype6 libfreetype6-dev zlib1g-dev zlib1g xvfb python-opengl ffmpeg libhdf5-dev && \ - ln -s /usr/lib/x86_64-linux-gnu/libz.so /lib/ && \ - ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /lib/ && \ - echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ - locale-gen && \ - wget -O - https://bootstrap.pypa.io/get-pip.py | python3 && \ - rm -rf /var/lib/apt/lists/* && \ - echo '#!/bin/bash\n\\n\echo\n\echo " $@"\n\echo\n\' > /browser && \ - chmod +x /browser - - -.PHONY: remove-dev-packages -remove-dev-packages: - pip3 uninstall -y cython && \ - apt-get remove -y cmake pkg-config flex bison curl libpng-dev \ - libjpeg-turbo8-dev zlib1g-dev libhdf5-dev libopenblas-dev gfortran \ - libfreetype6-dev libjpeg8-dev libffi-dev && \ - apt-get autoremove -y && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Install Python 3.9 -.PHONY: install-python3.9 -install-python3.9: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.9 python3.9-dev python3-distutils python3-setuptools - -# Install Python 3.8 -.PHONY: install-python3.8 -install-python3.8: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.8 python3.8-dev python3-distutils python3-setuptools - -# Install Python 3.7 -.PHONY: install-python3.7 -install-python3.7: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.7 python3.7-dev python3-distutils python3-setuptools - -# Install Python 3.6 -.PHONY: install-python3.6 -install-python3.6: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.6 python3.6-dev python3-distutils python3-setuptools \ - -# Install phantomjs for holoviews image save -.PHONY: install-phantomjs -install-phantomjs: - curl -sSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ - echo "deb https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee /etc/apt/sources.list.d/nodesource.list && \ - echo "deb-src https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee -a /etc/apt/sources.list.d/nodesource.list && \ - apt-get update && apt-get install -y nodejs && \ - npm install phantomjs --unsafe-perm && \ - npm install -g phantomjs-prebuilt --unsafe-perm - -# Install common python dependencies -.PHONY: install-python-libs -install-python-libs: - python3 -m pip install -U pip && \ - pip3 install --no-cache-dir setuptools wheel cython pipenv && \ - pip3 install --no-cache-dir matplotlib && \ - python3 -c "import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot" diff --git a/tests/examples/docker/test_mloq/target/WHAT_MLOQ_GENERATED.md b/tests/examples/docker/test_mloq/target/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index cc288b47..00000000 --- a/tests/examples/docker/test_mloq/target/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,4 +0,0 @@ -# What mloq generated for your project - -* `Dockerfile` - Docker container for the project -* `Makefile.docker` - Makefile for the Docker container setup diff --git a/tests/examples/docs/test_1/mloq.yaml b/tests/examples/docs/test_1/mloq.yaml deleted file mode 100644 index 8ec35e90..00000000 --- a/tests/examples/docs/test_1/mloq.yaml +++ /dev/null @@ -1,94 +0,0 @@ -globals: - project_name: test_docs - default_branch: master - owner: tester - author: tester-docs - email: test_docs@mail.com - description: test docs command - open_source: true - project_url: "http://example_url/docs_test" - -license: - disable: false - license: MIT - copyright_year: ${current_year:} - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - -project: - disable: false - docker: false # FIXME: depends on docker command - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - default_branch: ${globals.default_branch} - deploy_docs: true - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - requirements: - - None - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - -lint: - disable: false - docstring_checks: true - pyproject_extra: "" - project_name: ${globals.project_name} - -docker: - disable: false - docker_image: fragiletech/ubuntu20.04-base-py38 - docker_org: ${globals.owner} - -ci: - bot_name: ??? - bot_email: ??? - disable: false - vendor: github - ci_python_version: '3.8' - ci_ubuntu_version: ubuntu-20.04 - ci_extra: "" - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: ${docker.docker_org} diff --git a/tests/examples/docs/test_1/target/WHAT_MLOQ_GENERATED.md b/tests/examples/docs/test_1/target/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index 867a58f0..00000000 --- a/tests/examples/docs/test_1/target/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,7 +0,0 @@ -# What mloq generated for your project - -* `Makefile` - common make commands for building the documentation -* `conf.py` - configuration file for sphinx and doc plugins -* `index.md` - configuration file for sphinx and doc plugins -* `make.bat` - common make commands for building the documentation -* `requirements-docs.txt` - list of exact versions of the packages needed to build your documentation diff --git a/tests/examples/docs/test_1/target/docs/Makefile b/tests/examples/docs/test_1/target/docs/Makefile deleted file mode 100644 index 3c9dbc41..00000000 --- a/tests/examples/docs/test_1/target/docs/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: server -server: - python3 -m http.server --directory build/html/ - -.PHONY: test -test: - make html - make server \ No newline at end of file diff --git a/tests/examples/docs/test_1/target/docs/make.bat b/tests/examples/docs/test_1/target/docs/make.bat deleted file mode 100644 index 9534b018..00000000 --- a/tests/examples/docs/test_1/target/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/tests/examples/docs/test_1/target/docs/requirements-docs.txt b/tests/examples/docs/test_1/target/docs/requirements-docs.txt deleted file mode 100644 index f4d7d870..00000000 --- a/tests/examples/docs/test_1/target/docs/requirements-docs.txt +++ /dev/null @@ -1,6 +0,0 @@ -sphinx==4.3.0 -linkify-it-py==1.0.2 -myst-parser==0.15.2 -ruyaml==0.19.2 -sphinx-autoapi==1.8.4 -sphinx-rtd-theme==1.0.0 \ No newline at end of file diff --git a/tests/examples/docs/test_1/target/docs/source/conf.py b/tests/examples/docs/test_1/target/docs/source/conf.py deleted file mode 100644 index 5429776e..00000000 --- a/tests/examples/docs/test_1/target/docs/source/conf.py +++ /dev/null @@ -1,121 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys - - -sys.path.insert(0, os.path.abspath("../../")) -sys.setrecursionlimit(1500) - -# -- Project information ----------------------------------------------------- -project = "test_docs" -copyright = "2021, tester" -author = "tester-docs" - -# The short X.Y version -from test_docs.version import __version__ - - -version = __version__ -# The full version, including alpha/beta/rc tags -release = __version__ -# -- General configuration --------------------------------------------------- -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ["_build", "**.ipynb_checkpoints"] -# The master toctree document. -master_doc = "index" -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "autoapi.extension", - "sphinx.ext.doctest", - "sphinx.ext.intersphinx", - "sphinx.ext.todo", - "sphinx.ext.coverage", - "sphinx.ext.imgmath", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx.ext.autosectionlabel", - "sphinx.ext.autodoc.typehints", - "myst_parser", - # "sphinx.ext.githubpages", - # "m2r", -] -suppress_warnings = ["image.nonlocal_uri"] -autodoc_typehints = "description" -# Autoapi settings -autoapi_type = "python" -autoapi_dirs = ["../../test_docs"] -autoapi_add_toctree_entry = True -# Make use of custom templates -autoapi_template_dir = "_autoapi_templates" -exclude_patterns.append("_autoapi_templates/index.rst") - -# Ignore sphinx-autoapi warnings on multiple target description -suppress_warnings.append("ref.python") - -# Napoleon settings -napoleon_google_docstring = True -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = True -napoleon_include_private_with_doc = False -napoleon_include_special_with_doc = True -napoleon_use_admonition_for_examples = False -napoleon_use_admonition_for_notes = False -napoleon_use_admonition_for_references = False -napoleon_use_ivar = False -napoleon_use_param = True -napoleon_use_rtype = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_rtd_theme" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# myst_parser options -myst_heading_anchors = 2 -myst_enable_extensions = [ - "amsmath", - "colon_fence", - "deflist", - "dollarmath", - "html_admonition", - "html_image", - "linkify", - "replacements", - "smartquotes", - "substitution", -] - - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True diff --git a/tests/examples/docs/test_1/target/docs/source/index.md b/tests/examples/docs/test_1/target/docs/source/index.md deleted file mode 100644 index bf6402d0..00000000 --- a/tests/examples/docs/test_1/target/docs/source/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Welcome to test_docs - -test docs command - -```{toctree} ---- -maxdepth: 5 -caption: test_docs API ---- -autoapi/index.rst -``` \ No newline at end of file diff --git a/tests/examples/license/test_1/mloq.yaml b/tests/examples/license/test_1/mloq.yaml deleted file mode 100644 index 27dc19a2..00000000 --- a/tests/examples/license/test_1/mloq.yaml +++ /dev/null @@ -1,98 +0,0 @@ -globals: - project_name: test_license - default_branch: master - owner: tester - author: tester-docs - email: test_docs@mail.com - description: test docs command - open_source: true - project_url: "http://example_url/docs_test" - license: "MIT" - use_poetry: true - -license: - disable: false - license: MIT - copyright_year: ${current_year:} - copyright_holder: ${globals.owner} - project_name: ${globals.project_name} - project_url: ${globals.project_url} - email: ${globals.email} - -project: - disable: false - docker: false # FIXME: depends on docker command - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - requirements: - - None - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - -lint: - disable: false - docstring_checks: true - pyproject_extra: "" - project_name: ${globals.project_name} - -docker: - disable: false - docker_image: fragiletech/ubuntu20.04-base-py38 - docker_org: ${globals.owner} - -ci: - bot_name: my_bot - bot_email: my_bot_email - python_versions: ${package.python_versions} - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - ci_extra: "" - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: my_org - docker: true diff --git a/tests/examples/license/test_1/target/CODE_OF_CONDUCT.md b/tests/examples/license/test_1/target/CODE_OF_CONDUCT.md deleted file mode 100644 index 2cfbfe2e..00000000 --- a/tests/examples/license/test_1/target/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ - -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at test_docs@mail.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available -at [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/tests/examples/license/test_1/target/CONTRIBUTING.md b/tests/examples/license/test_1/target/CONTRIBUTING.md deleted file mode 100644 index 6a5ee6d5..00000000 --- a/tests/examples/license/test_1/target/CONTRIBUTING.md +++ /dev/null @@ -1,56 +0,0 @@ -# Contributing Guidelines - -test_license is [MIT licensed](LICENSE) and accepts -contributions via GitHub pull requests. This document outlines some of the -conventions on development workflow, commit message formatting, contact points, -and other resources to make it easier to get your contribution accepted. - -## Certificate of Origin - -By contributing to this project you agree to the [Developer Certificate of -Origin (DCO)](DCO.md). This document was created by the Linux Kernel community and is a -simple statement that you, as a contributor, have the legal right to make the -contribution. - -In order to show your agreement with the DCO you should include at the end of commit message, -the following line: `Signed-off-by: John Doe `, using your real name. - -This can be done easily using the [`-s`](https://github.com/git/git/blob/b2c150d3aa82f6583b9aadfecc5f8fa1c74aca09/Documentation/git-commit.txt#L154-L161) flag on the `git commit`. - - -## Support Channels - -The official support channels, for both users and contributors, are: - -- GitHub [issues](http://example_url/docs_test/issues)* - -*Before opening a new issue or submitting a new pull request, it's helpful to -search the project - it's likely that another user has already reported the -issue you're facing, or it's a known issue that we're already aware of. - - -## How to Contribute - -Pull Requests (PRs) are the main and exclusive way to contribute to the official project. -In order for a PR to be accepted it needs to pass a list of requirements: - -- The CI style check passes (run locally with `make check`). -- Code Coverage does not decrease. -- All the tests pass. -- Python code is formatted according to [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/). -- If the PR is a bug fix, it has to include a new unit test that fails before the patch is merged. -- If the PR is a new feature, it has to come with a suite of unit tests, that tests the new functionality. -- In any case, all the PRs have to pass the personal evaluation of at least one of the [maintainers](MAINTAINERS.md). - - -### Format of the commit message - -The commit summary must start with a capital letter and with a verb in present tense. No dot in the end. - -``` -Add a feature -Remove unused code -Fix a bug -``` - -Every commit details should describe what was changed, under which context and, if applicable, the GitHub issue it relates to. diff --git a/tests/examples/license/test_1/target/DCO.md b/tests/examples/license/test_1/target/DCO.md deleted file mode 100644 index e440da92..00000000 --- a/tests/examples/license/test_1/target/DCO.md +++ /dev/null @@ -1,36 +0,0 @@ -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -660 York Street, Suite 102, -San Francisco, CA 94110 USA - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with -this project or the open source license(s) involved. diff --git a/tests/examples/license/test_1/target/LICENSE b/tests/examples/license/test_1/target/LICENSE deleted file mode 100644 index 8b5cb01b..00000000 --- a/tests/examples/license/test_1/target/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 - 2021 tester - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/tests/examples/license/test_1/target/WHAT_MLOQ_GENERATED.md b/tests/examples/license/test_1/target/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index 63892221..00000000 --- a/tests/examples/license/test_1/target/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,6 +0,0 @@ -# What mloq generated for your project - -* `CODE_OF_CONDUCT.md` - behavioral rules and norms in open source projects -* `CONTRIBUTING.md` - technical manual on how to contrib to the open source project -* `DCO.md` - Developer Certificate of Origin - needed in open source projects to certify that the incoming contributions are legitimate -* `LICENSE` - license of the project diff --git a/tests/examples/lint/test_1/mloq.yaml b/tests/examples/lint/test_1/mloq.yaml deleted file mode 100644 index 32243f7e..00000000 --- a/tests/examples/lint/test_1/mloq.yaml +++ /dev/null @@ -1,95 +0,0 @@ -globals: - project_name: test_docs - default_branch: master - owner: tester - author: tester-docs - email: test_docs@mail.com - description: test docs command - open_source: true - project_url: "http://example_url/docs_test" - -license: - disable: false - license: MIT - copyright_year: ${current_year:} - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - -project: - disable: false - docker: false # FIXME: depends on docker command - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - requirements: - - None - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - -lint: - disable: false - docstring_checks: true - pyproject_extra: "" - project_name: ${globals.project_name} - -docker: - disable: false - docker_image: fragiletech/ubuntu20.04-base-py38 - docker_org: ${globals.owner} - -ci: - bot_name: my_bot - bot_email: my_bot_email - python_versions: ${package.python_versions} - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - ci_extra: "" - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: my_org - docker: true - diff --git a/tests/examples/mloq.yaml b/tests/examples/mloq.yaml deleted file mode 100644 index 1263da1f..00000000 --- a/tests/examples/mloq.yaml +++ /dev/null @@ -1,142 +0,0 @@ -globals: - project_name: mloq - default_branch: master - owner: FragileTech - author: FragileTech - email: guillem@fragile.tech - description: Package for initializing ML projects following ML Ops best practices. - open_source: true - project_url: https://github.com/FragileTech/ml-ops-quickstart - license: "MIT" - use_poetry: true - -license: - disable: false - license: MIT - copyright_year: 2020 - copyright_holder: ${globals.owner} - project_name: ${globals.project_name} - email: ${globals.email} - project_url: ${globals.project_url} - -project: - disable: false - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - project_url: ${globals.project_url} - tests: true - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - default_branch: ${globals.default_branch} - deploy_docs: true - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - license_classifier: "License :: OSI Approved :: MIT License" - -requirements: - disable: false - requirements: - - dogfood - -lint: - disable: false - docstring_checks: false - pyproject_extra: |- - [tool.flakehell.exceptions."**/assets/*"] - pycodestyle = ["-*"] - pyflakes = ["-*"] - "flake8*" = ["-*"] - project_name: ${globals.project_name} - makefile: true - -docker: - disable: false - cuda: ??? - cuda_image_type: "cudnn8-runtime-ubuntu20.04" - cuda_version: "11.2" - python_version: "3.8" - ubuntu_version: "20.04" - base_image: "???" - test: true - lint: true - jupyter: true - jupyter_password: ${globals.project_name} - project_name: ${globals.project_name} - docker_org: ${globals.owner} - requirements: ${requirements.requirements} - extra: "" - makefile: true - -ci: - bot_name: fragile-bot - bot_email: bot@fragile.tech - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: fragiletech - docker: true - python_versions: ${package.python_versions} - ci_extra: |- - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py \ No newline at end of file diff --git a/tests/examples/package/test_1/mloq.yaml b/tests/examples/package/test_1/mloq.yaml deleted file mode 100644 index c464b51a..00000000 --- a/tests/examples/package/test_1/mloq.yaml +++ /dev/null @@ -1,97 +0,0 @@ -globals: - project_name: test_package - default_branch: master - owner: tester - author: tester-package - email: test_package@mail.com - description: test package command - open_source: true - project_url: "http://example_url/package_test" - -license: - disable: false - license: MIT - copyright_year: ${current_year:} - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - project_name: ${globals.project_name} - project_url: ${globals.project_url} - email: ${globals.email} - -project: - disable: false - docker: false # FIXME: depends on docker command - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - requirements: - - None - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - license_classifier: "pepe" - -lint: - disable: false - docstring_checks: true - pyproject_extra: "" - project_name: ${globals.project_name} - -docker: - disable: false - docker_image: fragiletech/ubuntu20.04-base-py38 - docker_org: ${globals.owner} - -ci: - bot_name: my_bot - bot_email: my_bot_email - python_versions: ${package.python_versions} - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - ci_extra: "" - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: my_org - docker: true diff --git a/tests/examples/project/test_1/mloq.yaml b/tests/examples/project/test_1/mloq.yaml deleted file mode 100644 index 3920ec3c..00000000 --- a/tests/examples/project/test_1/mloq.yaml +++ /dev/null @@ -1,93 +0,0 @@ -globals: - project_name: test_project - default_branch: master - owner: tester - author: tester-project - email: test_project@mail.com - description: test project command - open_source: true - project_url: "http://example_url/project_test" - license: "MIT" - use_poetry: true - -license: - disable: false - license: MIT - copyright_year: ${current_year:} - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - -project: - disable: false - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: {globals.owner} - description: ${globals.description} - - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: false - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - requirements: - - None - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - -lint: - disable: false - projecttring_checks: true - pyproject_extra: "" - -docker: - disable: false - docker_image: fragiletech/ubuntu20.04-base-py38 - docker_org: ${globals.owner} - project_name: ${globals.project_name} - -ci: - bot_name: ??? - bot_email: ??? - disable: false - vendor: github - ci_python_version: '3.8' - ci_ubuntu_version: ubuntu-20.04 - ci_extra: "" - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: ${docker.docker_org} diff --git a/tests/examples/project/test_mloq/mloq.yaml b/tests/examples/project/test_mloq/mloq.yaml deleted file mode 100644 index b43d508d..00000000 --- a/tests/examples/project/test_mloq/mloq.yaml +++ /dev/null @@ -1,142 +0,0 @@ -globals: - project_name: mloq - default_branch: master - owner: FragileTech - author: FragileTech - email: guillem@fragile.tech - description: Package for initializing ML projects following ML Ops best practices. - open_source: true - project_url: https://github.com/FragileTech/ml-ops-quickstart - license: "MIT" - use_poetry: true - -license: - disable: false - license: MIT - copyright_year: 2020 - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - project_name: ${globals.project_name} - email: ${globals.email} - project_url: ${globals.project_url} - -project: - disable: false - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - project_url: ${globals.project_url} - tests: true - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - license_classifier: "License :: OSI Approved :: MIT License" - pipenv: true - -requirements: - disable: false - requirements: - - dogfood - -lint: - disable: false - docstring_checks: false - pyproject_extra: |- - [tool.flakehell.exceptions."**/assets/*"] - pycodestyle = ["-*"] - pyflakes = ["-*"] - "flake8*" = ["-*"] - project_name: ${globals.project_name} - makefile: true - -docker: - disable: false - cuda: ??? - cuda_image_type: "cudnn8-runtime-ubuntu20.04" - cuda_version: "11.2" - python_version: "3.8" - ubuntu_version: "20.04" - base_image: "???" - test: true - lint: true - jupyter: true - jupyter_password: ${globals.project_name} - project_name: ${globals.project_name} - docker_org: ${globals.owner} - requirements: ${requirements.requirements} - extra: "" - makefile: true - -ci: - bot_name: fragile-bot - bot_email: bot@fragile.tech - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: fragiletech - docker: true - python_versions: ${package.python_versions} - ci_extra: |- - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - rm generated/tests/test_main.py \ No newline at end of file diff --git a/tests/examples/requirements/test_dogfood/mloq.yaml b/tests/examples/requirements/test_dogfood/mloq.yaml deleted file mode 100644 index 9d88913c..00000000 --- a/tests/examples/requirements/test_dogfood/mloq.yaml +++ /dev/null @@ -1,119 +0,0 @@ -globals: - project_name: mloqq - default_branch: master - owner: FragileTech - author: FragileTech - email: guillem@fragile.tech - description: Package for initializing ML projects following ML Ops best practices. - open_source: true - project_url: https://github.com/FragileTech/ml-ops-quickstart - -license: - disable: false - license: MIT - copyright_year: 2020 - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - -project: - disable: false - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - -requirements: - disable: false - requirements: - - dogfood - -lint: - disable: false - docstring_checks: true - pyproject_extra: |- - [tool.flakehell.exceptions."**/assets/*"] - pycodestyle = ["-*"] - pyflakes = ["-*"] - "flake8*" = ["-*"] - -docker: - disable: false - docker_image: fragiletech/ubuntu20.04-base-py38 - docker_org: ${globals.owner} - -ci: - bot_name: fragile-bot - bot_email: bot@fragile.tech - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: fragiletech - docker: true - python_versions: ${package.python_versions} - ci_extra: |- - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff Makefile generated/Makefile - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff Dockerfile generated/Dockerfile - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - rm generated/mloq/tests/test_main.py \ No newline at end of file diff --git a/tests/examples/requirements/test_dogfood/target/requirements.txt b/tests/examples/requirements/test_dogfood/target/requirements.txt deleted file mode 100644 index 1ea496cf..00000000 --- a/tests/examples/requirements/test_dogfood/target/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -click==8.0.1 -flogging==0.0.14 -hydra-core==1.0.6 -invoke==1.5.0 -jinja2==3.0.1 -pre-commit==2.13.0 \ No newline at end of file diff --git a/tests/examples/requirements/test_ds_tf/mloq.yaml b/tests/examples/requirements/test_ds_tf/mloq.yaml deleted file mode 100644 index ea6a3ee6..00000000 --- a/tests/examples/requirements/test_ds_tf/mloq.yaml +++ /dev/null @@ -1,122 +0,0 @@ -globals: - project_name: mloqq - default_branch: master - owner: FragileTech - author: FragileTech - email: guillem@fragile.tech - description: Package for initializing ML projects following ML Ops best practices. - open_source: true - project_url: https://github.com/FragileTech/ml-ops-quickstart - -license: - disable: false - license: MIT - copyright_year: 2020 - copyright_holder: ${globals.owner} - open_source: ${globals.open_source} - -project: - disable: false - docker: false # FIXME: depends on docker command - license: ${license.license} # FIXME: depends on docker command - project_name: ${globals.project_name} - owner: ${globals.owner} - description: ${globals.description} - -mlflow: - disable: true - -docs: - disable: false - project_name: ${globals.project_name} - description: ${globals.description} - author: ${globals.author} - copyright_holder: ${license.copyright_holder} - copyright_year: ${license.copyright_year} - -git: - disable: true - git_init: false - git_push: false - git_message: Generate project files with mloq - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - -package: - disable: False - open_source: ${globals.open_source} - project_name: ${globals.project_name} - description: ${globals.description} - default_branch: ${globals.default_branch} - project_url: ${globals.project_url} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - license: ${license.license} - python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - pyproject_extra: "" - -requirements: - disable: false - requirements: - - tensorflow - - data-science - - dataviz - -lint: - disable: false - docstring_checks: true - pyproject_extra: |- - [tool.flakehell.exceptions."**/assets/*"] - pycodestyle = ["-*"] - pyflakes = ["-*"] - "flake8*" = ["-*"] - -docker: - disable: false - docker_image: fragiletech/ubuntu20.04-base-py38 - docker_org: ${globals.owner} - -ci: - bot_name: fragile-bot - bot_email: bot@fragile.tech - disable: false - vendor: github - ci_python_version: '3.8' - ubuntu_version: ubuntu-20.04 - open_source: ${globals.open_source} - project_name: ${globals.project_name} - default_branch: ${globals.default_branch} - owner: ${globals.owner} - author: ${globals.author} - email: ${globals.email} - project_url: ${globals.project_url} - docker_org: fragiletech - docker: true - python_versions: ${package.python_versions} - ci_extra: |- - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff Makefile generated/Makefile - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff Dockerfile generated/Dockerfile - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - rm generated/mloq/tests/test_main.py \ No newline at end of file diff --git a/tests/examples/requirements/test_ds_tf/target/requirements.txt b/tests/examples/requirements/test_ds_tf/target/requirements.txt deleted file mode 100644 index 3684fbe6..00000000 --- a/tests/examples/requirements/test_ds_tf/target/requirements.txt +++ /dev/null @@ -1,18 +0,0 @@ -bokeh==2.3.2 -holoviews==1.14.4 -hvplot==0.7.2 -matplotlib==3.4.2 -networkx==2.5.1 -numba==0.53.1 -numpy==1.20.3 -opencv-python==4.5.2.52 -pandas==1.2.4 -panel==0.11.3 -param==1.10.1 -pillow-simd==7.0.0.post3 -plotly==4.14.3 -scikit-learn==0.24.2 -scipy==1.6.3 -selenium==3.141.0 -streamz==0.6.2 -tensorflow==2.5.0 \ No newline at end of file diff --git a/tests/examples/setup/test_mloq/target/.codecov.yml b/tests/examples/setup/test_mloq/target/.codecov.yml deleted file mode 100644 index 52e41711..00000000 --- a/tests/examples/setup/test_mloq/target/.codecov.yml +++ /dev/null @@ -1,13 +0,0 @@ -comment: false -ignore: -- tests -coverage: - status: - patch: off - project: - default: - target: auto - threshold: 0% - informational: true - paths: - - mloq_test_setup diff --git a/tests/examples/setup/test_mloq/target/.github/workflows/push.yml b/tests/examples/setup/test_mloq/target/.github/workflows/push.yml deleted file mode 100644 index 0448fd10..00000000 --- a/tests/examples/setup/test_mloq/target/.github/workflows/push.yml +++ /dev/null @@ -1,299 +0,0 @@ -name: Push - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - PROJECT_NAME: mloq_test_setup - PROJECT_DIR: mloq_test_setup - VERSION_FILE: mloq_test_setup/version.py - DEFAULT_BRANCH: master - BOT_NAME: fragile-bot - BOT_EMAIL: bot@fragile.tech - DOCKER_ORG: fragiletech - PIP_CACHE: | - ~/.cache/pip - ~/.local/bin - ~/.local/lib/python3.*/site-packages - -jobs: - style-check: - name: Style check - if: "!contains(github.event.head_commit.message, 'Bump version')" - runs-on: ubuntu-20.04 - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: "3.8" - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-lint-${{ hashFiles('requirements-lint.txt') }} - restore-keys: ubuntu-20.04-pip-lint- - - name: Install lint dependencies - run: | - set -x - pip install -r requirements-lint.txt - - - name: Run style check and linter - run: | - set -x - make check - - pytest: - name: Run Pytest - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - strategy: - matrix: - python-version: ['3.6', '3.7', '3.8', '3.9'] - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-test-${{ matrix.python-version }}-${{ hashFiles('requirements.txt', 'requirements-test.txt') }} - restore-keys: ubuntu-20.04-pip-test- - - name: Install test and package dependencies - run: | - set -x - pip install -r requirements-test.txt -r requirements.txt - pip install . - - name: Additional setup - run: | - set -x - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py - - - name: Test with pytest - run: | - set -x - make test-codecov - - - name: Upload coverage report - if: ${{ matrix.python-version=='3.8' }} - uses: codecov/codecov-action@v1 - - test-docker: - name: Test Docker container - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - uses: actions/checkout@v2 - - name: Build container - run: | - set -x - make docker-build - - name: Run tests - run: | - set -x - make docker-test - - build-test-package: - name: Build and test the package - needs: style-check - runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'Bump version')" - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: actions/cache - uses: actions/cache@v2 - with: - path: ${{ env.PIP_CACHE }} - key: ubuntu-20.04-pip-test-3.8-${{ hashFiles('requirements.txt', 'requirements-test.txt') }} - restore-keys: ubuntu-20.04-pip-test- - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel bump2version - - - name: Create unique version for test.pypi - run: | - set -x - current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2) - ts=$(date +%s) - new_version="$current_version$ts" - bumpversion --current-version $current_version --new-version $new_version patch $VERSION_FILE - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to TestPyPI - env: - TEST_PYPI_PASS: ${{ secrets.TEST_PYPI_PASS }} - if: "'$TEST_PYPI_PASS' != ''" - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.TEST_PYPI_PASS }} - repository_url: https://test.pypi.org/legacy/ - skip_existing: true - - - name: Install dependencies - run: | - set -x - python -m pip install dist/*.whl -r requirements-test.txt - - name: Additional setup - run: | - set -x - git config --global user.name "Bot" - git config --global user.email "bot@fragile.tech" - git config --global init.defaultBranch master - mkdir generated - mloq setup generated -f mloq.yaml - diff .github/workflows/push.yml generated/.github/workflows/push.yml - diff requirements.txt generated/requirements.txt - diff requirements-lint.txt generated/requirements-lint.txt - diff requirements-test.txt generated/requirements-test.txt - diff pyproject.toml generated/pyproject.toml - diff DCO.md generated/DCO.md - diff CODE_OF_CONDUCT.md generated/CODE_OF_CONDUCT.md - diff CONTRIBUTING.md generated/CONTRIBUTING.md - diff LICENSE generated/LICENSE - diff .pre-commit-config.yaml generated/.pre-commit-config.yaml - diff .codecov.yml generated/.codecov.yml - diff .gitignore generated/.gitignore - diff WHAT_MLOQ_GENERATED.md generated/WHAT_MLOQ_GENERATED.md - diff Dockerfile generated/Dockerfile - diff Makefile generated/Makefile - rm generated/tests/test_main.py - - - name: Test package - run: | - set -x - rm -rf $PROJECT_DIR - make test - - bump-version: - name: Bump package version - env: - BOT_AUTH_TOKEN: ${{ secrets.BOT_AUTH_TOKEN }} - if: "!contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$BOT_AUTH_TOKEN' != ''" - runs-on: ubuntu-20.04 - needs: - - pytest - - build-test-package - - test-docker - steps: - - name: actions/checkout - uses: actions/checkout@v2 - with: - persist-credentials: false - fetch-depth: 100 - - name: current_version - run: | - set -x - echo "current_version=$(grep __version__ $VERSION_FILE | cut -d\" -f2)" >> $GITHUB_ENV - echo "version_file=$VERSION_FILE" >> $GITHUB_ENV - echo 'bot_name="${BOT_NAME}"' >> $GITHUB_ENV - echo 'bot_email="${BOT_EMAIL}"' >> $GITHUB_ENV - - name: FragileTech/bump-version - uses: FragileTech/bump-version@main - with: - current_version: "${{ env.current_version }}" - files: "${{ env.version_file }}" - commit_name: "${{ env.bot_name }}" - commit_email: "${{ env.bot_email }}" - login: "${{ env.bot_name }}" - token: "${{ secrets.BOT_AUTH_TOKEN }}" - - push-docker: - name: Push Docker container - runs-on: ubuntu-20.04 - env: - DOCKERHUB_PASS: ${{ secrets.DOCKERHUB_PASS }} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$DOCKERHUB_PASS' != ''" - steps: - - uses: actions/checkout@v2 - - name: Login to DockerHub - run: | - set -x - docker login -u "${{ secrets.DOCKERHUB_LOGIN }}" -p "${{ secrets.DOCKERHUB_PASS }}" docker.io - - - name: Build container - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-build VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG - - name: Push images - - run: | - set -x - CONTAINER_VERSION=v$(grep __version__ $VERSION_FILE | cut -d\" -f2) - make docker-push VERSION=$CONTAINER_VERSION PROJECT=$PROJECT_NAME DOCKER_ORG=$DOCKER_ORG - - release-package: - name: Release PyPI package - env: - PYPI_PASS: ${{ secrets.PYPI_PASS }} - if: "contains(github.event.head_commit.message, 'Bump version') && github.ref == 'refs/heads/master' && '$PYPI_PASS' != ''" - runs-on: ubuntu-20.04 - steps: - - name: actions/checkout - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install dependencies - run: | - set -x - python -m pip install -U pip - python -m pip install -U setuptools twine wheel - - - name: Build package - run: | - set -x - python setup.py --version - python setup.py bdist_wheel sdist --format=gztar - twine check dist/* - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.PYPI_PASS }} diff --git a/tests/examples/setup/test_mloq/target/.gitignore b/tests/examples/setup/test_mloq/target/.gitignore deleted file mode 100644 index 90992bca..00000000 --- a/tests/examples/setup/test_mloq/target/.gitignore +++ /dev/null @@ -1,130 +0,0 @@ -# This file is here just for the reference -WHAT_MLOQ_GENERATED.md - -#Mac OS -*.DS_Store - -#PyCharm IDE -.idea/ - -# Documentation build templates -docs/_build/ -docs/build/ - -# Byte-compiled / optimized / DLL templates -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these templates are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docsrc/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed templates -*.sage.py - -# dotenv -.env - -# virtualenv -.venv -venv/ -ENV/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ - -# CI -.ci - -# Notebooks by default are ignored -*.ipynb - -# lock files -*.lock - -# hydra command history -outputs/ - -*.pck -*.npy diff --git a/tests/examples/setup/test_mloq/target/.pre-commit-config.yaml b/tests/examples/setup/test_mloq/target/.pre-commit-config.yaml deleted file mode 100644 index 98e5a947..00000000 --- a/tests/examples/setup/test_mloq/target/.pre-commit-config.yaml +++ /dev/null @@ -1,10 +0,0 @@ -repos: -- repo: https://github.com/psf/black - rev: 20.8b1 - hooks: - - id: black -- repo: https://github.com/PyCQA/isort - rev: 5.8.0 - hooks: - - id: isort - args: ["."] diff --git a/tests/examples/setup/test_mloq/target/CODE_OF_CONDUCT.md b/tests/examples/setup/test_mloq/target/CODE_OF_CONDUCT.md deleted file mode 100644 index 6b10d223..00000000 --- a/tests/examples/setup/test_mloq/target/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ - -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at guillem@fragile.tech. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available -at [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/tests/examples/setup/test_mloq/target/CONTRIBUTING.md b/tests/examples/setup/test_mloq/target/CONTRIBUTING.md deleted file mode 100644 index ea95c565..00000000 --- a/tests/examples/setup/test_mloq/target/CONTRIBUTING.md +++ /dev/null @@ -1,56 +0,0 @@ -# Contributing Guidelines - -mloq_test_setup is [MIT licensed](LICENSE) and accepts -contributions via GitHub pull requests. This document outlines some of the -conventions on development workflow, commit message formatting, contact points, -and other resources to make it easier to get your contribution accepted. - -## Certificate of Origin - -By contributing to this project you agree to the [Developer Certificate of -Origin (DCO)](DCO.md). This document was created by the Linux Kernel community and is a -simple statement that you, as a contributor, have the legal right to make the -contribution. - -In order to show your agreement with the DCO you should include at the end of commit message, -the following line: `Signed-off-by: John Doe `, using your real name. - -This can be done easily using the [`-s`](https://github.com/git/git/blob/b2c150d3aa82f6583b9aadfecc5f8fa1c74aca09/Documentation/git-commit.txt#L154-L161) flag on the `git commit`. - - -## Support Channels - -The official support channels, for both users and contributors, are: - -- GitHub [issues](https://github.com/FragileTech/ml-ops-quickstart/issues)* - -*Before opening a new issue or submitting a new pull request, it's helpful to -search the project - it's likely that another user has already reported the -issue you're facing, or it's a known issue that we're already aware of. - - -## How to Contribute - -Pull Requests (PRs) are the main and exclusive way to contribute to the official project. -In order for a PR to be accepted it needs to pass a list of requirements: - -- The CI style check passes (run locally with `make check`). -- Code Coverage does not decrease. -- All the tests pass. -- Python code is formatted according to [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/). -- If the PR is a bug fix, it has to include a new unit test that fails before the patch is merged. -- If the PR is a new feature, it has to come with a suite of unit tests, that tests the new functionality. -- In any case, all the PRs have to pass the personal evaluation of at least one of the [maintainers](MAINTAINERS.md). - - -### Format of the commit message - -The commit summary must start with a capital letter and with a verb in present tense. No dot in the end. - -``` -Add a feature -Remove unused code -Fix a bug -``` - -Every commit details should describe what was changed, under which context and, if applicable, the GitHub issue it relates to. diff --git a/tests/examples/setup/test_mloq/target/DCO.md b/tests/examples/setup/test_mloq/target/DCO.md deleted file mode 100644 index e440da92..00000000 --- a/tests/examples/setup/test_mloq/target/DCO.md +++ /dev/null @@ -1,36 +0,0 @@ -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -660 York Street, Suite 102, -San Francisco, CA 94110 USA - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with -this project or the open source license(s) involved. diff --git a/tests/examples/setup/test_mloq/target/Dockerfile b/tests/examples/setup/test_mloq/target/Dockerfile deleted file mode 100644 index df4bbc22..00000000 --- a/tests/examples/setup/test_mloq/target/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM ubuntu:20.04 -ARG JUPYTER_PASSWORD="mloq_test_setup" -ENV BROWSER=/browser \ - LC_ALL=en_US.UTF-8 \ - LANG=en_US.UTF-8 -COPY Makefile.docker Makefile -COPY . mloq_test_setup/ - -RUN apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends make cmake && \ - make install-python3.8 && \ - make install-common-dependencies && \ - make install-python-libs - -RUN cd mloq_test_setup \ - && python3 -m pip install -U pip \ - && pip3 install -r requirements-lint.txt \ - && pip3 install -r requirements-test.txt \ - && pip3 install -r requirements.txt \ - && pip install ipython jupyter \ - && pip3 install -e . \ - && git config --global init.defaultBranch master \ - && git config --global user.name "Whoever" \ - && git config --global user.email "whoever@fragile.tech" - -RUN make remove-dev-packages - -RUN mkdir /root/.jupyter && \ - echo 'c.NotebookApp.token = "'${JUPYTER_PASSWORD}'"' > /root/.jupyter/jupyter_notebook_config.py -CMD jupyter notebook --allow-root --port 8080 --ip 0.0.0.0 diff --git a/tests/examples/setup/test_mloq/target/LICENSE b/tests/examples/setup/test_mloq/target/LICENSE deleted file mode 100644 index 03295f07..00000000 --- a/tests/examples/setup/test_mloq/target/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 - 2021 FragileTech - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/tests/examples/setup/test_mloq/target/Makefile.docker b/tests/examples/setup/test_mloq/target/Makefile.docker deleted file mode 100644 index 67b30b5a..00000000 --- a/tests/examples/setup/test_mloq/target/Makefile.docker +++ /dev/null @@ -1,79 +0,0 @@ -current_dir = $(shell pwd) - -PROJECT = dockerfiles -VERSION ?= latest -DOCKER_TAG = None -PYTHON_VERSION = "3.8" -UBUNTU_NAME = $(lsb_release -s -c) - -# Install system packages -.PHONY: install-common-dependencies -install-common-dependencies: - apt-get update && \ - apt-get install -y --no-install-suggests --no-install-recommends \ - ca-certificates locales pkg-config apt-utils gcc g++ wget make cmake git curl flex ssh gpgv \ - libffi-dev libjpeg-turbo-progs libjpeg8-dev libjpeg-turbo8 libjpeg-turbo8-dev gnupg2 \ - libpng-dev libpng16-16 libglib2.0-0 bison gfortran lsb-release \ - libsm6 libxext6 libxrender1 libfontconfig1 libhdf5-dev libopenblas-base libopenblas-dev \ - libfreetype6 libfreetype6-dev zlib1g-dev zlib1g xvfb python-opengl ffmpeg libhdf5-dev && \ - ln -s /usr/lib/x86_64-linux-gnu/libz.so /lib/ && \ - ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /lib/ && \ - echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ - locale-gen && \ - wget -O - https://bootstrap.pypa.io/get-pip.py | python3 && \ - rm -rf /var/lib/apt/lists/* && \ - echo '#!/bin/bash\n\\n\echo\n\echo " $@"\n\echo\n\' > /browser && \ - chmod +x /browser - - -.PHONY: remove-dev-packages -remove-dev-packages: - pip3 uninstall -y cython && \ - apt-get remove -y cmake pkg-config flex bison curl libpng-dev \ - libjpeg-turbo8-dev zlib1g-dev libhdf5-dev libopenblas-dev gfortran \ - libfreetype6-dev libjpeg8-dev libffi-dev && \ - apt-get autoremove -y && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Install Python 3.9 -.PHONY: install-python3.9 -install-python3.9: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.9 python3.9-dev python3-distutils python3-setuptools - -# Install Python 3.8 -.PHONY: install-python3.8 -install-python3.8: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.8 python3.8-dev python3-distutils python3-setuptools - -# Install Python 3.7 -.PHONY: install-python3.7 -install-python3.7: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.7 python3.7-dev python3-distutils python3-setuptools - -# Install Python 3.6 -.PHONY: install-python3.6 -install-python3.6: - apt-get install -y --no-install-suggests --no-install-recommends \ - python3.6 python3.6-dev python3-distutils python3-setuptools \ - -# Install phantomjs for holoviews image save -.PHONY: install-phantomjs -install-phantomjs: - curl -sSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ - echo "deb https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee /etc/apt/sources.list.d/nodesource.list && \ - echo "deb-src https://deb.nodesource.com/node_10.x ${UBUNTU_NAME} main" | tee -a /etc/apt/sources.list.d/nodesource.list && \ - apt-get update && apt-get install -y nodejs && \ - npm install phantomjs --unsafe-perm && \ - npm install -g phantomjs-prebuilt --unsafe-perm - -# Install common python dependencies -.PHONY: install-python-libs -install-python-libs: - python3 -m pip install -U pip && \ - pip3 install --no-cache-dir setuptools wheel cython pipenv && \ - pip3 install --no-cache-dir matplotlib && \ - python3 -c "import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot" diff --git a/tests/examples/setup/test_mloq/target/README.md b/tests/examples/setup/test_mloq/target/README.md deleted file mode 100644 index 93ffa969..00000000 --- a/tests/examples/setup/test_mloq/target/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# mloq_test_setup -[![Documentation Status](https://readthedocs.org/projects/mloq_test_setup/badge/?version=latest)](https://mloq_test_setup.readthedocs.io/en/latest/?badge=latest) -[![Code coverage](https://codecov.io/github/FragileTech/mloq_test_setup/coverage.svg)](https://codecov.io/github/FragileTech/mloq_test_setup) -[![PyPI package](https://badgen.net/pypi/v/mloq_test_setup)](https://pypi.org/project/mloq_test_setup/) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) -[![license: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) - -Package for initializing ML projects following ML Ops best practices. \ No newline at end of file diff --git a/tests/examples/setup/test_mloq/target/WHAT_MLOQ_GENERATED.md b/tests/examples/setup/test_mloq/target/WHAT_MLOQ_GENERATED.md deleted file mode 100644 index e728b075..00000000 --- a/tests/examples/setup/test_mloq/target/WHAT_MLOQ_GENERATED.md +++ /dev/null @@ -1,29 +0,0 @@ -# What mloq generated for your project - -* `.codecov.yml` - configuration of CodeCov service to track the code coverage -* `.gitignore` - list of files and directories ignored by Git operations -* `.pre-commit-config.yaml` - Git pre-commit hooks configuration -* `CODE_OF_CONDUCT.md` - behavioral rules and norms in open source projects -* `CONTRIBUTING.md` - technical manual on how to contrib to the open source project -* `DCO.md` - Developer Certificate of Origin - needed in open source projects to certify that the incoming contributions are legitimate -* `Dockerfile` - Docker container for the project -* `LICENSE` - license of the project -* `Makefile` - common make commands for building the documentation -* `Makefile` - common make commands for development -* `Makefile.docker` - Makefile for the Docker container setup -* `README.md` - README -* `__init__.py` - Python package header for the project module -* `__init__.py` - Python package header for the test module -* `__main__.py` - Python package executable entry point -* `conf.py` - configuration file for sphinx and doc plugins -* `index.md` - configuration file for sphinx and doc plugins -* `make.bat` - common make commands for building the documentation -* `push.yml` - GitHub Actions continuous integration workflow file -* `pyproject.toml` - configuration of various development tools: linters, formatters -* `requirements-docs.txt` - list of exact versions of the packages needed to build your documentation -* `requirements-lint.txt` - list of exact versions of the packages used to check your code style -* `requirements-test.txt` - list of exact versions of the packages needed to run your test suite -* `requirements.txt` - list of exact versions of the packages on which your project depends -* `setup.py` - Python package installation metadata -* `test_main.py` - Unit test of the python package executable entry point -* `version.py` - defines the version of the package that is incremented on each push diff --git a/tests/examples/setup/test_mloq/target/docs/Makefile b/tests/examples/setup/test_mloq/target/docs/Makefile deleted file mode 100644 index 3c9dbc41..00000000 --- a/tests/examples/setup/test_mloq/target/docs/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: server -server: - python3 -m http.server --directory build/html/ - -.PHONY: test -test: - make html - make server \ No newline at end of file diff --git a/tests/examples/setup/test_mloq/target/docs/make.bat b/tests/examples/setup/test_mloq/target/docs/make.bat deleted file mode 100644 index 9534b018..00000000 --- a/tests/examples/setup/test_mloq/target/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/tests/examples/setup/test_mloq/target/docs/requirements-docs.txt b/tests/examples/setup/test_mloq/target/docs/requirements-docs.txt deleted file mode 100644 index f4d7d870..00000000 --- a/tests/examples/setup/test_mloq/target/docs/requirements-docs.txt +++ /dev/null @@ -1,6 +0,0 @@ -sphinx==4.3.0 -linkify-it-py==1.0.2 -myst-parser==0.15.2 -ruyaml==0.19.2 -sphinx-autoapi==1.8.4 -sphinx-rtd-theme==1.0.0 \ No newline at end of file diff --git a/tests/examples/setup/test_mloq/target/docs/source/conf.py b/tests/examples/setup/test_mloq/target/docs/source/conf.py deleted file mode 100644 index efa974c0..00000000 --- a/tests/examples/setup/test_mloq/target/docs/source/conf.py +++ /dev/null @@ -1,121 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys - - -sys.path.insert(0, os.path.abspath("../../")) -sys.setrecursionlimit(1500) - -# -- Project information ----------------------------------------------------- -project = "mloq_test_setup" -copyright = "2020, FragileTech" -author = "FragileTech" - -# The short X.Y version -from mloq_test_setup.version import __version__ - - -version = __version__ -# The full version, including alpha/beta/rc tags -release = __version__ -# -- General configuration --------------------------------------------------- -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ["_build", "**.ipynb_checkpoints"] -# The master toctree document. -master_doc = "index" -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "autoapi.extension", - "sphinx.ext.doctest", - "sphinx.ext.intersphinx", - "sphinx.ext.todo", - "sphinx.ext.coverage", - "sphinx.ext.imgmath", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx.ext.autosectionlabel", - "sphinx.ext.autodoc.typehints", - "myst_parser", - # "sphinx.ext.githubpages", - # "m2r", -] -suppress_warnings = ["image.nonlocal_uri"] -autodoc_typehints = "description" -# Autoapi settings -autoapi_type = "python" -autoapi_dirs = ["../../mloq_test_setup"] -autoapi_add_toctree_entry = True -# Make use of custom templates -autoapi_template_dir = "_autoapi_templates" -exclude_patterns.append("_autoapi_templates/index.rst") - -# Ignore sphinx-autoapi warnings on multiple target description -suppress_warnings.append("ref.python") - -# Napoleon settings -napoleon_google_docstring = True -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = True -napoleon_include_private_with_doc = False -napoleon_include_special_with_doc = True -napoleon_use_admonition_for_examples = False -napoleon_use_admonition_for_notes = False -napoleon_use_admonition_for_references = False -napoleon_use_ivar = False -napoleon_use_param = True -napoleon_use_rtype = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_rtd_theme" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# myst_parser options -myst_heading_anchors = 2 -myst_enable_extensions = [ - "amsmath", - "colon_fence", - "deflist", - "dollarmath", - "html_admonition", - "html_image", - "linkify", - "replacements", - "smartquotes", - "substitution", -] - - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True diff --git a/tests/examples/setup/test_mloq/target/docs/source/index.md b/tests/examples/setup/test_mloq/target/docs/source/index.md deleted file mode 100644 index aa1cb62a..00000000 --- a/tests/examples/setup/test_mloq/target/docs/source/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Welcome to mloq_test_setup - -Package for initializing ML projects following ML Ops best practices. - -```{toctree} ---- -maxdepth: 5 -caption: mloq_test_setup API ---- -autoapi/index.rst -``` \ No newline at end of file diff --git a/tests/examples/setup/test_mloq/target/pyproject.toml b/tests/examples/setup/test_mloq/target/pyproject.toml deleted file mode 100644 index 7068a6cb..00000000 --- a/tests/examples/setup/test_mloq/target/pyproject.toml +++ /dev/null @@ -1,98 +0,0 @@ -[build-system] -requires = ["setuptools >= 50.3.2", "wheel >= 0.29.0"] -build-backend = "setuptools.build_meta" - -# black is the tool to format the source code -[tool.black] -line-length = 99 -target-version = ['py36', 'py37', 'py38'] -include = '\.pyi?$' -exclude = ''' -/( - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist -)/ -''' -# isort orders and lints imports -[tool.isort] -profile = "black" -line_length = 99 -multi_line_output = 3 -order_by_type = false -force_alphabetical_sort_within_sections = true -force_sort_within_sections = true -combine_as_imports = true -include_trailing_comma = true -color_output = true -lines_after_imports = 2 -honor_noqa = true - -# Code coverage config -[tool.coverage.run] -branch = true -source = ["mloq_test_setup"] - -[tool.coverage.report] -exclude_lines =["no cover", - 'raise NotImplementedError', - 'if __name__ == "__main__":'] -ignore_errors = true -omit = ["tests/*"] - -# Flakehell config -[tool.flakehell] -# optionally inherit from remote config (or local if you want) -base = "https://raw.githubusercontent.com/life4/flakehell/master/pyproject.toml" -# specify any flake8 options. For example, exclude "example.py": -exclude = [".git", "docs", ".ipynb*", "*.ipynb", ".pytest_cache"] -format = "grouped" # make output nice -max_line_length = 99 # show line of source code in output -show_source = true -inline_quotes='"' -import_order_style = "appnexus" -application_package_names = ["mloq_test_setup"] -application_import_names = ["mloq_test_setup"] -# Fix AttributeError: 'Namespace' object has no attribute 'extended_default_ignore' -extended_default_ignore=[] - -[tool.flakehell.plugins] -"flake8*" = ["+*"] -pylint = ["+*"] -pyflakes = ["+*"] -pycodestyle = ["+*" , "-D100", "-D104", "-D301", "-W503", "-W504"] - -[tool.flakehell.exceptions."**/__init__.py"] -pyflakes = ["-F401"] - -# No docs in the tests. No unused imports (otherwise pytest fixtures raise errors). -[tool.flakehell.exceptions."**/tests/*"] -pycodestyle = ["-D*"] -"flake8*" = ["-D*"] -pylint = ["-D*"] -pyflakes = ["-F401", "-F811"] - -[tool.pylint.master] -ignore = 'tests' -load-plugins =' pylint.extensions.docparams' - -[tool.pylint.messages_control] -disable = 'all,' -enable = """, - missing-param-doc, - differing-param-doc, - differing-type-doc, - missing-return-doc, - """ - -[tool.flakehell.exceptions."**/assets/*"] -pycodestyle = ["-*"] -pyflakes = ["-*"] -"flake8*" = ["-*"] \ No newline at end of file diff --git a/tests/examples/setup/test_mloq/target/requirements-lint.txt b/tests/examples/setup/test_mloq/target/requirements-lint.txt deleted file mode 100644 index 999db0bf..00000000 --- a/tests/examples/setup/test_mloq/target/requirements-lint.txt +++ /dev/null @@ -1,13 +0,0 @@ -flake8==3.9.2 -flake8-bugbear==21.9.2 -flake8-docstrings==1.6.0 -flake8-import-order==0.18.1 -flake8-quotes==3.3.1 -flake8-commas==2.1.0 -isort==5.10.1 -pylint==2.11.1 -pydocstyle==6.1.1 -pycodestyle==2.7.0 -flakehell==0.9.0 -black==21.10b0 -pre-commit==2.15.0 \ No newline at end of file diff --git a/tests/examples/setup/test_mloq/target/requirements-test.txt b/tests/examples/setup/test_mloq/target/requirements-test.txt deleted file mode 100644 index b45aa16a..00000000 --- a/tests/examples/setup/test_mloq/target/requirements-test.txt +++ /dev/null @@ -1,6 +0,0 @@ -psutil==5.8.0 -pytest==6.2.5 -pytest-cov==3.0.0 -pytest-xdist==2.4.0 -pytest-rerunfailures==10.2 -hypothesis==6.24.6 \ No newline at end of file diff --git a/tests/examples/setup/test_mloq/target/requirements.txt b/tests/examples/setup/test_mloq/target/requirements.txt deleted file mode 100644 index aeb5f013..00000000 --- a/tests/examples/setup/test_mloq/target/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -click==8.0.3 -flogging==0.0.14 -hydra-core==1.1.1 -invoke==1.6.0 -jinja2==3.0.3 -pre-commit==2.15.0 \ No newline at end of file diff --git a/tests/examples/setup/test_mloq/target/setup.py b/tests/examples/setup/test_mloq/target/setup.py deleted file mode 100644 index 42018561..00000000 --- a/tests/examples/setup/test_mloq/target/setup.py +++ /dev/null @@ -1,43 +0,0 @@ -"""mloq_test_setup package installation metadata.""" -from importlib.machinery import SourceFileLoader -from pathlib import Path - -from setuptools import find_packages, setup - - -version = SourceFileLoader( - "mloq_test_setup.version", - str(Path(__file__).parent / "mloq_test_setup" / "version.py"), -).load_module() - -with open(Path(__file__).with_name("README.md"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="mloq_test_setup", - description="Package for initializing ML projects following ML Ops best practices.", - long_description=long_description, - long_description_content_type="text/markdown", - packages=find_packages(), - version=version.__version__, - license="MIT", - author="FragileTech", - author_email="guillem@fragile.tech", - url="https://github.com/FragileTech/ml-ops-quickstart", - keywords=["Machine learning", "artificial intelligence"], - test_suite="tests", - tests_require=["pytest>=5.3.5", "hypothesis>=5.6.0"], - extras_require={}, - install_requires=[], - package_data={"": ["README.md"]}, - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Topic :: Software Development :: Libraries", - ], -) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..4b8fb50a --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,7 @@ +import subprocess + + +def test_main(): + cmd_name = "mloq" + output = subprocess.check_output([cmd_name, "foo", "foobar"], text=True) + assert cmd_name in output or "foobar\n" in output diff --git a/tests/test_command.py b/tests/test_command.py deleted file mode 100644 index 7802c526..00000000 --- a/tests/test_command.py +++ /dev/null @@ -1,109 +0,0 @@ -from pathlib import Path - -from omegaconf import DictConfig -import pytest - -from mloq.command import Command -from mloq.writer import CMDRecord - - -conf = {} - - -def is_namedtuple_instance(x): - t = type(x) - b = t.__bases__ - if len(b) != 1 or b[0] != tuple: - return False - f = getattr(t, "_fields", None) - if not isinstance(f, tuple): - return False - return all(type(n) == str for n in f) - - -@pytest.fixture(params=[(Command, conf)], scope="function") -def command_and_config(request): - command_cls, conf_dict = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, config - - -@pytest.fixture(params=[(Command, conf, None)], scope="function") -def command_and_example(request): - command_cls, conf_dict, example = request.param - config = DictConfig(conf_dict) - record = CMDRecord(config) - command = command_cls(record=record) - return command, example - - -class TestCommand: - def test_class_attributes(self, command_and_config): - command, config = command_and_config - assert isinstance(command.cmd_name, str) - assert isinstance(command.files, tuple) - assert isinstance(command.config, DictConfig) - assert isinstance(command.directories, tuple) - if len(command.directories) > 0: - for directory in command.directories: - assert isinstance(directory, Path) - - def test_parse_config(self, command_and_config): - command, config = command_and_config - if command.__class__.__name__ == "SetupCMD": - return - command.parse_config() - for key in command.config.keys(): - conf_record = getattr(command.record.config, command.cmd_name) - assert conf_record[key] == config[command.cmd_name][key], key - - def test_files_present_in_record(self, command_and_config): - command, _ = command_and_config - command.record_files() - - file_names = [f.name for f in command.files] - file_srcs = [f.src for f in command.files] - file_dsts = [f.dst for f in command.files] - for f in command.record.files.values(): - assert f.name in file_names - assert f.src in file_srcs - assert f.dst in file_dsts - - def test_record_directories(self, command_and_config): - command, _ = command_and_config - command.record_directories() - for directory in command.directories: - assert directory in command.record.directories - for directory in command.record.directories: - assert directory in command.directories - - def test_run(self, command_and_config): - command, _ = command_and_config - record = command.run() - for directory in command.directories: - assert directory in record.directories - - file_names = [f.name for f in command.files] - file_srcs = [f.src for f in command.files] - file_dsts = [f.dst for f in command.files] - for f in record.files.values(): - assert f.name in file_names - assert f.src in file_srcs - assert f.dst in file_dsts - - def test_files_have_correct_path(self, command_and_example): - if not command_and_example: - return - command, example_files = command_and_example - if example_files is None: - return - record = command.run() - for path, file in record.files.items(): - assert path in example_files - assert example_files[path] == file - - for path, file in example_files.items(): - assert path in record.files - assert record.files[path] == file diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 00000000..bd6e607c --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,5 @@ +from mloq import compute + + +def test_compute(): + assert compute(["a", "bc", "abc"]) == "abc" diff --git a/tests/test_record.py b/tests/test_record.py deleted file mode 100644 index 084f92ec..00000000 --- a/tests/test_record.py +++ /dev/null @@ -1,141 +0,0 @@ -from pathlib import Path - -from omegaconf import DictConfig -import pytest - -from mloq.commands.package import setup_py -from mloq.files import File, mloq_yml -from mloq.record import CMDRecord, Ledger -from tests.commands.test_docs import docs_conf, docs_conf_with_globals - - -@pytest.fixture(scope="function") -def ledger(): - return Ledger() - - -_files = [mloq_yml, setup_py] - - -@pytest.fixture(params=_files, scope="class") -def file(request): - return request.param - - -@pytest.fixture(scope="class") -def files(): - return _files - - -config_examples = [None, DictConfig({}), DictConfig(docs_conf), DictConfig(docs_conf_with_globals)] -config_ids = ["config-is-None", "empty-dict", "docs-conf", "docs-conf-with-globals"] - - -@pytest.fixture(params=config_examples, scope="function", ids=config_ids) -def record(request): - return CMDRecord(request.param) - - -@pytest.fixture(scope="class") -def directories(): - return [Path(), Path("test directory"), "test_str_directory"] - - -@pytest.fixture(params=config_examples[1:], scope="class") -def config(request): - return request.param - - -class TestLedger: - def test_files(self, ledger, files): - for file in files: - ledger.register(file) - assert isinstance(ledger.files, list) - assert len(ledger.files) == len(files) - for item in ledger.files: - assert isinstance(item, tuple) - assert isinstance(item[0], str) - assert isinstance(item[1], str) - - def test_register_no_description(self, ledger, file): - ledger.register(file) - assert (str(Path(file.dst)), file.description) in ledger.files - assert (Path(file.dst), file.description) == ledger._files[0] - - def test_register_description_str(self, ledger): - file, description = f"example/{mloq_yml.dst}", "test_description" - ledger.register(file, description=description) - assert (str(Path(file)), description) in ledger.files - assert (Path(file), description) == ledger._files[0] - - def test_register_description_file(self, ledger, file): - description = f"example/{mloq_yml.dst}", "test_description" - ledger.register(file, description=description) - assert (str(Path(file.dst)), description) in ledger.files - assert (Path(file.dst), description) == ledger._files[0] - - def test_no_description_fails(self, ledger): - with pytest.raises(ValueError): - ledger.register("error_file.txt") - - -class TestCMDRecord: - def test_attribute_types(self, record, files, directories): - assert isinstance(record.config, DictConfig) - assert isinstance(record.files, dict) - for file in files: - record.register_file(file, Path()) - for k, v in record.files.items(): - assert isinstance(k, Path) - assert isinstance(v, File) - for directory in directories: - record.register_directory(directory) - assert isinstance(record.directories, list) - for directory in record.directories: - assert isinstance(directory, Path) - - def test_register_file_no_description(self, record, files): - example_path = Path("miau") - for file in files: - record.register_file(file, example_path) - for file, (k, v) in zip(files, record.files.items()): - assert example_path / file.dst == k - assert file == v - - def test_register_file_with_description(self, record, files): - example_path = Path("miau") - example_description = "This is an example description" - for file in files: - record.register_file(file, example_path, description=example_description) - for file, (k, v) in zip(files, record.files.items()): - assert example_path / file.dst == k - example_file = File( - name=file.name, - src=file.src, - dst=file.dst, - description=example_description, - is_static=file.is_static, - ) - assert example_file == v - - def test_register_fails_no_description(self, record, file): - example_file = File( - name=file.name, - src=file.src, - dst=file.dst, - description="", - is_static=file.is_static, - ) - with pytest.raises(ValueError): - record.register_file(example_file, path=Path()) - - def test_register_directory(self, record, directories): - for directory in directories: - record.register_directory(directory) - for directory, _ in zip(record.directories, directories): - assert directory == Path(directory) - - def test_update_config(self, record, config): - record.update_config(config) - # TODO: Find a nice way to test this - # assert record.config == config diff --git a/tests/test_runner.py b/tests/test_runner.py deleted file mode 100644 index 5c70d62d..00000000 --- a/tests/test_runner.py +++ /dev/null @@ -1,132 +0,0 @@ -import filecmp -import os -import os.path -from pathlib import Path -import shutil -import tempfile - -from omegaconf import DictConfig, OmegaConf -import pytest - -from mloq.commands import CiCMD, DockerCMD, DocsCMD, LicenseCMD, LintCMD, ProjectCMD, SetupCMD -from mloq.files import mloq_yml, read_file -from mloq.runner import load_config, run_command - - -COMMANDS = [CiCMD, DockerCMD, DocsCMD, SetupCMD] # [ProjectCMD, LintCMD, LicenseCMD, SetupCMD] - -CMD_CLASS_TO_NAME = { - CiCMD: "ci", - DockerCMD: "docker", - DocsCMD: "docs", - SetupCMD: "setup", - ProjectCMD: "project", - LintCMD: "lint", - LicenseCMD: "license", -} - - -def generate_command_examples(commands): - examples = [] - docs_test_examples = Path(__file__).parent / "examples" - for cmd in commands: - for example in os.listdir(docs_test_examples / CMD_CLASS_TO_NAME[cmd]): - examples.append((cmd, docs_test_examples / CMD_CLASS_TO_NAME[cmd] / example)) - return examples - - -def id_func(example): - command, folder = example - return f"{command.cmd_name}-{folder.name}" - - -@pytest.fixture(scope="function", params=generate_command_examples(COMMANDS), ids=id_func) -def command_example(request): - return request.param - - -def dir_trees_are_equal(dir1, dir2): - """ - Compare two directories recursively. Files in each directory are - assumed to be equal if their names and contents are equal. - - @param dir1: First directory path - @param dir2: Second directory path - - @return: True if the directory trees are the same and - there were no errors while accessing the directories or files, - False otherwise. - """ - - dirs_cmp = filecmp.dircmp(dir1, dir2) - if ( - len(dirs_cmp.left_only) > 0 - or len(dirs_cmp.right_only) > 0 - or len(dirs_cmp.funny_files) > 0 - ): - return False - (_, mismatch, errors) = filecmp.cmpfiles(dir1, dir2, dirs_cmp.common_files, shallow=False) - if len(mismatch) > 0 or len(errors) > 0: - return False - for common_dir in dirs_cmp.common_dirs: - new_dir1 = os.path.join(dir1, common_dir) - new_dir2 = os.path.join(dir2, common_dir) - if not dir_trees_are_equal(new_dir1, new_dir2): - return False - return True - - -class TestLoadConfig: - def test_load_config_empty_config(self): - config = load_config("this_dir_does_not_exist", []) - assert isinstance(config, DictConfig) - example = OmegaConf.load(mloq_yml.src) - assert config == example - - def test_load_config_file(self): - temp_dir = tempfile.TemporaryDirectory() - with open(Path(temp_dir.name) / mloq_yml.dst, "w") as f: - f.write(read_file(mloq_yml)) - # Load configuration providing a directory containing an mloq.yaml file - config = load_config(temp_dir.name, []) - assert isinstance(config, DictConfig) - example = OmegaConf.load(mloq_yml.src) - assert config == example - # Load configuration providing the path to the target mloq.yaml file - config = load_config(Path(temp_dir.name) / mloq_yml.dst, []) - assert isinstance(config, DictConfig) - example = OmegaConf.load(mloq_yml.src) - assert config == example - temp_dir.cleanup() - - -class TestRunCommand: - def test_run_command(self, command_example): - if command_example is None: - return - cls, example_dir = command_example - _run_cmd = run_command(cls, use_click=False) - config_file = Path(example_dir) / mloq_yml.dst - target_example_path = Path(example_dir) / "target" - temp_dir = tempfile.TemporaryDirectory() - target_path = Path(temp_dir.name) / "target" - os.makedirs(target_path) - _run_cmd( - config_file=config_file, - output_directory=target_path, - overwrite=False, - only_config=False, - interactive=False, - hydra_args="", - ) - - # The setup command create a test folder and a Makefile that interfere with make test. - # Let's remove them so the project does not crash. - if cls == SetupCMD: - shutil.rmtree(target_path / "tests") - os.remove(target_path / "Makefile") - assert ( - dir_trees_are_equal(str(target_example_path), str(target_path)), - set(os.listdir(target_path)) - set(os.listdir(target_example_path)), - ) - temp_dir.cleanup() diff --git a/tests/test_writer.py b/tests/test_writer.py deleted file mode 100644 index aeaf8e49..00000000 --- a/tests/test_writer.py +++ /dev/null @@ -1,20 +0,0 @@ -from tempfile import TemporaryDirectory - -import pytest - -from mloq.record import CMDRecord -from mloq.writer import Writer -from tests.test_record import config_examples - - -@pytest.fixture(params=config_examples, scope="function") -def writer(request): - temp_dir = TemporaryDirectory() - record = CMDRecord(request.param) - yield Writer(record=record, path=temp_dir.name) - temp_dir.cleanup() - - -class TestWriter: - def test_init(self, writer): - pass From 01ae524f7197bb01fe3140d34d6beb698a57eb0b Mon Sep 17 00:00:00 2001 From: guillemdb Date: Wed, 2 Oct 2024 12:57:55 +0200 Subject: [PATCH 04/12] Add docs requirements Signed-off-by: guillemdb --- docs/requirements.txt | 113 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..38e82580 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,113 @@ +accessible-pygments==0.0.5 +alabaster==0.7.16 +astroid==3.3.4 +asttokens==2.4.1 +attrs==24.2.0 +autoapi==2.0.1 +babel==2.16.0 +beautifulsoup4==4.12.3 +certifi==2024.8.30 +cfgv==3.4.0 +charset-normalizer==3.3.2 +click==8.1.7 +comm==0.2.2 +debugpy==1.8.6 +decorator==5.1.1 +distlib==0.3.8 +distro==1.9.0 +docutils==0.20.1 +exceptiongroup==1.2.2 +executing==2.1.0 +fastjsonschema==2.20.0 +filelock==3.16.1 +flogging==0.0.23 +greenlet==3.1.1 +identify==2.6.1 +idna==3.10 +imagesize==1.4.1 +importlib-metadata==8.5.0 +ipykernel==6.29.5 +ipython==8.27.0 +jedi==0.19.1 +jinja2==3.1.4 +jsonschema==4.23.0 +jsonschema-specifications==2023.12.1 +jupyter-book==1.0.2 +jupyter-cache==1.0.0 +jupyter-client==8.6.3 +jupyter-core==5.7.2 +latexcodec==3.0.0 +linkify-it-py==2.0.3 +markdown-it-py==3.0.0 +markupsafe==2.1.5 +matplotlib-inline==0.1.7 +mdit-py-plugins==0.4.2 +mdurl==0.1.2 +-e file:///home/guillem/ml-ops-quickstart +myst-nb==1.1.2 +myst-parser==2.0.0 +nbclient==0.10.0 +nbformat==5.10.4 +nest-asyncio==1.6.0 +nodeenv==1.9.1 +packaging==24.1 +parso==0.8.4 +pexpect==4.9.0 +platformdirs==4.3.6 +pre-commit==3.8.0 +prompt-toolkit==3.0.48 +psutil==6.0.0 +ptyprocess==0.7.0 +pure-eval==0.2.3 +pybtex==0.24.0 +pybtex-docutils==1.0.3 +pydata-sphinx-theme==0.15.4 +pygments==2.18.0 +python-dateutil==2.9.0.post0 +pyyaml==6.0.2 +pyzmq==26.2.0 +referencing==0.35.1 +requests==2.32.3 +rpds-py==0.20.0 +ruyaml==0.91.0 +setuptools==75.1.0 +six==1.16.0 +snowballstemmer==2.2.0 +soupsieve==2.6 +sphinx==7.4.7 +sphinx-autoapi==3.3.2 +sphinx-autodoc2==0.5.0 +sphinx-book-theme==1.1.3 +sphinx-comments==0.0.3 +sphinx-copybutton==0.5.2 +sphinx-design==0.6.1 +sphinx-external-toc==1.0.1 +sphinx-jupyterbook-latex==1.0.0 +sphinx-multitoc-numbering==0.1.3 +sphinx-rtd-theme==2.0.0 +sphinx-thebe==0.3.1 +sphinx-togglebutton==0.3.2 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-bibtex==2.6.3 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-jquery==4.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-mermaid==0.9.2 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 +sphinxext-opengraph==0.9.1 +sqlalchemy==2.0.35 +stack-data==0.6.3 +tabulate==0.9.0 +tomli==2.0.2 +tornado==6.4.1 +traitlets==5.14.3 +typing-extensions==4.12.2 +uc-micro-py==1.0.3 +urllib3==2.2.3 +virtualenv==20.26.6 +wcwidth==0.2.13 +wheel==0.44.0 +xxhash==3.5.0 +zipp==3.20.2 From a8f2664252ca72ae853b140f55a35396edc93794 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Wed, 2 Oct 2024 13:11:22 +0200 Subject: [PATCH 05/12] Add submodules. Update requirements Signed-off-by: guillemdb --- .gitmodules | 6 + mloq-template | 1 + pyproject.toml | 4 +- requirements-dev.lock | 469 ++++++++++++++++++++++++++++++++++++++++++ requirements.lock | 389 +++++++++++++++++++++++++++++++++++ templates/mlops | 1 + 6 files changed, 868 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 160000 mloq-template create mode 100644 requirements-dev.lock create mode 100644 requirements.lock create mode 160000 templates/mlops diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..f281a01d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "templates/mlops"] + path = templates/mlops + url = https://github.com/FragileTech/mloq-cookiecutter.git +[submodule "mloq-template"] + path = mloq-template + url = https://github.com/FragileTech/mloq-template.git diff --git a/mloq-template b/mloq-template new file mode 160000 index 00000000..0dd780b4 --- /dev/null +++ b/mloq-template @@ -0,0 +1 @@ +Subproject commit 0dd780b40352f8080c214070f53b83e13654353a diff --git a/pyproject.toml b/pyproject.toml index 544b768b..bf882c25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,8 +44,8 @@ dynamic = ["version"] # direct dependencies of this package, installed when users `pip install ml-ops-quickstart` later. dependencies = [ "flogging", - "pre-commit", - "click", + "hydraclick", + "cookiecutter", ] # ToDo: Modify according to your needs! [project.urls] # important URLs for this project diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 00000000..27cff1dc --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,469 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: true +# with-sources: false +# generate-hashes: false +# universal: true + +-e file:. +accessible-pygments==0.0.5 + # via pydata-sphinx-theme +alabaster==0.7.16 + # via sphinx +antlr4-python3-runtime==4.9.3 + # via hydra-core + # via omegaconf +anyio==4.6.0 + # via httpx +appnope==0.1.4 ; platform_system == 'Darwin' + # via ipykernel +arrow==1.3.0 + # via cookiecutter +astroid==3.3.4 + # via sphinx-autoapi + # via sphinx-autodoc2 +asttokens==2.4.1 + # via stack-data +attrs==24.2.0 + # via jsonschema + # via jupyter-cache + # via referencing +autoapi==2.0.1 + # via ml-ops-quickstart +babel==2.16.0 + # via pydata-sphinx-theme + # via sphinx +backports-tarfile==1.2.0 ; python_full_version < '3.12' + # via jaraco-context +beautifulsoup4==4.12.3 + # via pydata-sphinx-theme +binaryornot==0.4.4 + # via cookiecutter +certifi==2024.8.30 + # via httpcore + # via httpx + # via requests +cffi==1.17.1 ; (platform_python_implementation != 'PyPy' and sys_platform == 'linux') or platform_python_implementation == 'PyPy' or implementation_name == 'pypy' + # via cryptography + # via pyzmq + # via zstandard +chardet==5.2.0 + # via binaryornot +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via cookiecutter + # via hatch + # via hydraclick + # via jupyter-book + # via jupyter-cache + # via sphinx-external-toc + # via userpath +colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows' + # via click + # via ipython + # via pytest + # via sphinx +comm==0.2.2 + # via ipykernel +cookiecutter==2.6.0 + # via ml-ops-quickstart +coverage==7.6.1 + # via pytest-cov +cryptography==43.0.1 ; sys_platform == 'linux' + # via secretstorage +debugpy==1.8.6 + # via ipykernel +decorator==5.1.1 + # via ipython +distlib==0.3.8 + # via virtualenv +distro==1.9.0 + # via ruyaml +docutils==0.20.1 + # via myst-parser + # via pybtex-docutils + # via pydata-sphinx-theme + # via sphinx + # via sphinx-rtd-theme + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +exceptiongroup==1.2.2 ; python_full_version < '3.11' + # via anyio + # via ipython + # via pytest +execnet==2.1.1 + # via pytest-xdist +executing==2.1.0 + # via stack-data +fastjsonschema==2.20.0 + # via nbformat +filelock==3.16.1 + # via virtualenv +flogging==0.0.23 + # via ml-ops-quickstart +greenlet==3.1.1 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64') + # via sqlalchemy +h11==0.14.0 + # via httpcore +hatch==1.12.0 +hatchling==1.25.0 + # via hatch +httpcore==1.0.6 + # via httpx +httpx==0.27.2 + # via hatch +hydra-core==1.3.2 + # via hydraclick +hydraclick==0.0.8 + # via ml-ops-quickstart +hyperlink==21.0.0 + # via hatch +idna==3.10 + # via anyio + # via httpx + # via hyperlink + # via requests +imagesize==1.4.1 + # via sphinx +importlib-metadata==8.5.0 + # via jupyter-cache + # via keyring + # via myst-nb +iniconfig==2.0.0 + # via pytest +ipykernel==6.29.5 + # via myst-nb +ipython==8.27.0 + # via ipykernel + # via myst-nb +jaraco-classes==3.4.0 + # via keyring +jaraco-context==6.0.1 + # via keyring +jaraco-functools==4.1.0 + # via keyring +jedi==0.19.1 + # via ipython +jeepney==0.8.0 ; sys_platform == 'linux' + # via keyring + # via secretstorage +jinja2==3.1.4 + # via cookiecutter + # via jupyter-book + # via myst-parser + # via sphinx + # via sphinx-autoapi +jsonschema==4.23.0 + # via jupyter-book + # via nbformat +jsonschema-specifications==2023.12.1 + # via jsonschema +jupyter-book==1.0.2 + # via ml-ops-quickstart +jupyter-cache==1.0.0 + # via ml-ops-quickstart + # via myst-nb +jupyter-client==8.6.3 + # via ipykernel + # via nbclient +jupyter-core==5.7.2 + # via ipykernel + # via jupyter-client + # via nbclient + # via nbformat +keyring==25.4.1 + # via hatch +latexcodec==3.0.0 + # via pybtex +linkify-it-py==2.0.3 + # via jupyter-book + # via ml-ops-quickstart +markdown-it-py==3.0.0 + # via mdit-py-plugins + # via myst-parser + # via rich +markupsafe==2.1.5 + # via jinja2 +matplotlib-inline==0.1.7 + # via ipykernel + # via ipython +mdit-py-plugins==0.4.2 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +more-itertools==10.5.0 + # via jaraco-classes + # via jaraco-functools +mypy==1.11.2 + # via ml-ops-quickstart +mypy-extensions==1.0.0 + # via mypy +myst-nb==1.1.2 + # via jupyter-book + # via ml-ops-quickstart +myst-parser==2.0.0 + # via jupyter-book + # via ml-ops-quickstart + # via myst-nb +nbclient==0.10.0 + # via jupyter-cache + # via myst-nb +nbformat==5.10.4 + # via jupyter-cache + # via myst-nb + # via nbclient +nest-asyncio==1.6.0 + # via ipykernel +omegaconf==2.3.0 + # via hydra-core +packaging==24.1 + # via hatch + # via hatchling + # via hydra-core + # via ipykernel + # via pydata-sphinx-theme + # via pytest + # via sphinx + # via sphinx-jupyterbook-latex +parso==0.8.4 + # via jedi +pathspec==0.12.1 + # via hatchling +pexpect==4.9.0 + # via hatch + # via ipython +platformdirs==4.3.6 + # via hatch + # via jupyter-core + # via virtualenv +pluggy==1.5.0 + # via hatchling + # via pytest +prompt-toolkit==3.0.48 + # via ipython +psutil==6.0.0 + # via ipykernel +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data +pybtex==0.24.0 + # via pybtex-docutils + # via sphinxcontrib-bibtex +pybtex-docutils==1.0.3 + # via sphinxcontrib-bibtex +pycparser==2.22 ; (platform_python_implementation != 'PyPy' and sys_platform == 'linux') or platform_python_implementation == 'PyPy' or implementation_name == 'pypy' + # via cffi +pydata-sphinx-theme==0.15.4 + # via ml-ops-quickstart + # via sphinx-book-theme +pygments==2.18.0 + # via accessible-pygments + # via ipython + # via pydata-sphinx-theme + # via rich + # via sphinx +pytest==8.3.3 + # via ml-ops-quickstart + # via pytest-cov + # via pytest-xdist +pytest-cov==5.0.0 + # via ml-ops-quickstart +pytest-xdist==3.6.1 + # via ml-ops-quickstart +python-dateutil==2.9.0.post0 + # via arrow + # via jupyter-client +python-slugify==8.0.4 + # via cookiecutter +pywin32==306 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + # via jupyter-core +pywin32-ctypes==0.2.3 ; sys_platform == 'win32' + # via keyring +pyyaml==6.0.2 + # via cookiecutter + # via jupyter-book + # via jupyter-cache + # via myst-nb + # via myst-parser + # via omegaconf + # via pybtex + # via sphinx-autoapi + # via sphinx-external-toc +pyzmq==26.2.0 + # via ipykernel + # via jupyter-client +referencing==0.35.1 + # via jsonschema + # via jsonschema-specifications +requests==2.32.3 + # via cookiecutter + # via sphinx +rich==13.9.1 + # via cookiecutter + # via hatch +rpds-py==0.20.0 + # via jsonschema + # via referencing +ruff==0.6.8 + # via ml-ops-quickstart +ruyaml==0.91.0 + # via ml-ops-quickstart +secretstorage==3.3.3 ; sys_platform == 'linux' + # via keyring +setuptools==75.1.0 + # via ruyaml + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +shellingham==1.5.4 + # via hatch +six==1.16.0 + # via asttokens + # via pybtex + # via python-dateutil +sniffio==1.3.1 + # via anyio + # via httpx +snowballstemmer==2.2.0 + # via sphinx +soupsieve==2.6 + # via beautifulsoup4 +sphinx==7.4.7 + # via autoapi + # via jupyter-book + # via ml-ops-quickstart + # via myst-nb + # via myst-parser + # via pydata-sphinx-theme + # via sphinx-autoapi + # via sphinx-book-theme + # via sphinx-comments + # via sphinx-copybutton + # via sphinx-design + # via sphinx-external-toc + # via sphinx-jupyterbook-latex + # via sphinx-multitoc-numbering + # via sphinx-rtd-theme + # via sphinx-thebe + # via sphinx-togglebutton + # via sphinxcontrib-bibtex + # via sphinxcontrib-jquery + # via sphinxext-opengraph +sphinx-autoapi==3.3.2 + # via ml-ops-quickstart +sphinx-autodoc2==0.5.0 + # via ml-ops-quickstart +sphinx-book-theme==1.1.3 + # via jupyter-book + # via ml-ops-quickstart +sphinx-comments==0.0.3 + # via jupyter-book +sphinx-copybutton==0.5.2 + # via jupyter-book + # via ml-ops-quickstart +sphinx-design==0.6.1 + # via jupyter-book +sphinx-external-toc==1.0.1 + # via jupyter-book +sphinx-jupyterbook-latex==1.0.0 + # via jupyter-book +sphinx-multitoc-numbering==0.1.3 + # via jupyter-book +sphinx-rtd-theme==2.0.0 + # via ml-ops-quickstart +sphinx-thebe==0.3.1 + # via jupyter-book +sphinx-togglebutton==0.3.2 + # via jupyter-book + # via ml-ops-quickstart +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-bibtex==2.6.3 + # via jupyter-book + # via ml-ops-quickstart +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via sphinx +sphinxcontrib-jquery==4.1 + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-mermaid==0.9.2 + # via ml-ops-quickstart +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via sphinx +sphinxext-opengraph==0.9.1 + # via ml-ops-quickstart +sqlalchemy==2.0.35 + # via jupyter-cache +stack-data==0.6.3 + # via ipython +tabulate==0.9.0 + # via jupyter-cache +text-unidecode==1.3 + # via python-slugify +tomli==2.0.2 ; python_full_version <= '3.11' + # via coverage + # via hatchling + # via mypy + # via pytest + # via sphinx + # via sphinx-autodoc2 +tomli-w==1.0.0 + # via hatch +tomlkit==0.13.2 + # via hatch +tornado==6.4.1 + # via ipykernel + # via jupyter-client +traitlets==5.14.3 + # via comm + # via ipykernel + # via ipython + # via jupyter-client + # via jupyter-core + # via matplotlib-inline + # via nbclient + # via nbformat +trove-classifiers==2024.9.12 + # via hatchling +types-python-dateutil==2.9.0.20240906 + # via arrow +typing-extensions==4.12.2 + # via anyio + # via astroid + # via ipython + # via mypy + # via myst-nb + # via pydata-sphinx-theme + # via rich + # via sphinx-autodoc2 + # via sqlalchemy +uc-micro-py==1.0.3 + # via linkify-it-py +urllib3==2.2.3 + # via requests +userpath==1.9.2 + # via hatch +uv==0.4.18 + # via hatch +virtualenv==20.26.6 + # via hatch +wcwidth==0.2.13 + # via prompt-toolkit +wheel==0.44.0 + # via sphinx-togglebutton +xxhash==3.5.0 + # via flogging +zipp==3.20.2 + # via importlib-metadata +zstandard==0.23.0 + # via hatch diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 00000000..32a743b0 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,389 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: true +# with-sources: false +# generate-hashes: false +# universal: true + +-e file:. +accessible-pygments==0.0.5 + # via pydata-sphinx-theme +alabaster==0.7.16 + # via sphinx +antlr4-python3-runtime==4.9.3 + # via hydra-core + # via omegaconf +appnope==0.1.4 ; platform_system == 'Darwin' + # via ipykernel +arrow==1.3.0 + # via cookiecutter +astroid==3.3.4 + # via sphinx-autoapi + # via sphinx-autodoc2 +asttokens==2.4.1 + # via stack-data +attrs==24.2.0 + # via jsonschema + # via jupyter-cache + # via referencing +autoapi==2.0.1 + # via ml-ops-quickstart +babel==2.16.0 + # via pydata-sphinx-theme + # via sphinx +beautifulsoup4==4.12.3 + # via pydata-sphinx-theme +binaryornot==0.4.4 + # via cookiecutter +certifi==2024.8.30 + # via requests +cffi==1.17.1 ; implementation_name == 'pypy' + # via pyzmq +chardet==5.2.0 + # via binaryornot +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via cookiecutter + # via hydraclick + # via jupyter-book + # via jupyter-cache + # via sphinx-external-toc +colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows' + # via click + # via ipython + # via pytest + # via sphinx +comm==0.2.2 + # via ipykernel +cookiecutter==2.6.0 + # via ml-ops-quickstart +coverage==7.6.1 + # via pytest-cov +debugpy==1.8.6 + # via ipykernel +decorator==5.1.1 + # via ipython +distro==1.9.0 + # via ruyaml +docutils==0.20.1 + # via myst-parser + # via pybtex-docutils + # via pydata-sphinx-theme + # via sphinx + # via sphinx-rtd-theme + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +exceptiongroup==1.2.2 ; python_full_version < '3.11' + # via ipython + # via pytest +execnet==2.1.1 + # via pytest-xdist +executing==2.1.0 + # via stack-data +fastjsonschema==2.20.0 + # via nbformat +flogging==0.0.23 + # via ml-ops-quickstart +greenlet==3.1.1 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64') + # via sqlalchemy +hydra-core==1.3.2 + # via hydraclick +hydraclick==0.0.8 + # via ml-ops-quickstart +idna==3.10 + # via requests +imagesize==1.4.1 + # via sphinx +importlib-metadata==8.5.0 + # via jupyter-cache + # via myst-nb +iniconfig==2.0.0 + # via pytest +ipykernel==6.29.5 + # via myst-nb +ipython==8.27.0 + # via ipykernel + # via myst-nb +jedi==0.19.1 + # via ipython +jinja2==3.1.4 + # via cookiecutter + # via jupyter-book + # via myst-parser + # via sphinx + # via sphinx-autoapi +jsonschema==4.23.0 + # via jupyter-book + # via nbformat +jsonschema-specifications==2023.12.1 + # via jsonschema +jupyter-book==1.0.2 + # via ml-ops-quickstart +jupyter-cache==1.0.0 + # via ml-ops-quickstart + # via myst-nb +jupyter-client==8.6.3 + # via ipykernel + # via nbclient +jupyter-core==5.7.2 + # via ipykernel + # via jupyter-client + # via nbclient + # via nbformat +latexcodec==3.0.0 + # via pybtex +linkify-it-py==2.0.3 + # via jupyter-book + # via ml-ops-quickstart +markdown-it-py==3.0.0 + # via mdit-py-plugins + # via myst-parser + # via rich +markupsafe==2.1.5 + # via jinja2 +matplotlib-inline==0.1.7 + # via ipykernel + # via ipython +mdit-py-plugins==0.4.2 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +mypy==1.11.2 + # via ml-ops-quickstart +mypy-extensions==1.0.0 + # via mypy +myst-nb==1.1.2 + # via jupyter-book + # via ml-ops-quickstart +myst-parser==2.0.0 + # via jupyter-book + # via ml-ops-quickstart + # via myst-nb +nbclient==0.10.0 + # via jupyter-cache + # via myst-nb +nbformat==5.10.4 + # via jupyter-cache + # via myst-nb + # via nbclient +nest-asyncio==1.6.0 + # via ipykernel +omegaconf==2.3.0 + # via hydra-core +packaging==24.1 + # via hydra-core + # via ipykernel + # via pydata-sphinx-theme + # via pytest + # via sphinx + # via sphinx-jupyterbook-latex +parso==0.8.4 + # via jedi +pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' + # via ipython +platformdirs==4.3.6 + # via jupyter-core +pluggy==1.5.0 + # via pytest +prompt-toolkit==3.0.48 + # via ipython +psutil==6.0.0 + # via ipykernel +ptyprocess==0.7.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' + # via pexpect +pure-eval==0.2.3 + # via stack-data +pybtex==0.24.0 + # via pybtex-docutils + # via sphinxcontrib-bibtex +pybtex-docutils==1.0.3 + # via sphinxcontrib-bibtex +pycparser==2.22 ; implementation_name == 'pypy' + # via cffi +pydata-sphinx-theme==0.15.4 + # via ml-ops-quickstart + # via sphinx-book-theme +pygments==2.18.0 + # via accessible-pygments + # via ipython + # via pydata-sphinx-theme + # via rich + # via sphinx +pytest==8.3.3 + # via ml-ops-quickstart + # via pytest-cov + # via pytest-xdist +pytest-cov==5.0.0 + # via ml-ops-quickstart +pytest-xdist==3.6.1 + # via ml-ops-quickstart +python-dateutil==2.9.0.post0 + # via arrow + # via jupyter-client +python-slugify==8.0.4 + # via cookiecutter +pywin32==306 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + # via jupyter-core +pyyaml==6.0.2 + # via cookiecutter + # via jupyter-book + # via jupyter-cache + # via myst-nb + # via myst-parser + # via omegaconf + # via pybtex + # via sphinx-autoapi + # via sphinx-external-toc +pyzmq==26.2.0 + # via ipykernel + # via jupyter-client +referencing==0.35.1 + # via jsonschema + # via jsonschema-specifications +requests==2.32.3 + # via cookiecutter + # via sphinx +rich==13.9.1 + # via cookiecutter +rpds-py==0.20.0 + # via jsonschema + # via referencing +ruff==0.6.8 + # via ml-ops-quickstart +ruyaml==0.91.0 + # via ml-ops-quickstart +setuptools==75.1.0 + # via ruyaml + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +six==1.16.0 + # via asttokens + # via pybtex + # via python-dateutil +snowballstemmer==2.2.0 + # via sphinx +soupsieve==2.6 + # via beautifulsoup4 +sphinx==7.4.7 + # via autoapi + # via jupyter-book + # via ml-ops-quickstart + # via myst-nb + # via myst-parser + # via pydata-sphinx-theme + # via sphinx-autoapi + # via sphinx-book-theme + # via sphinx-comments + # via sphinx-copybutton + # via sphinx-design + # via sphinx-external-toc + # via sphinx-jupyterbook-latex + # via sphinx-multitoc-numbering + # via sphinx-rtd-theme + # via sphinx-thebe + # via sphinx-togglebutton + # via sphinxcontrib-bibtex + # via sphinxcontrib-jquery + # via sphinxext-opengraph +sphinx-autoapi==3.3.2 + # via ml-ops-quickstart +sphinx-autodoc2==0.5.0 + # via ml-ops-quickstart +sphinx-book-theme==1.1.3 + # via jupyter-book + # via ml-ops-quickstart +sphinx-comments==0.0.3 + # via jupyter-book +sphinx-copybutton==0.5.2 + # via jupyter-book + # via ml-ops-quickstart +sphinx-design==0.6.1 + # via jupyter-book +sphinx-external-toc==1.0.1 + # via jupyter-book +sphinx-jupyterbook-latex==1.0.0 + # via jupyter-book +sphinx-multitoc-numbering==0.1.3 + # via jupyter-book +sphinx-rtd-theme==2.0.0 + # via ml-ops-quickstart +sphinx-thebe==0.3.1 + # via jupyter-book +sphinx-togglebutton==0.3.2 + # via jupyter-book + # via ml-ops-quickstart +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-bibtex==2.6.3 + # via jupyter-book + # via ml-ops-quickstart +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via sphinx +sphinxcontrib-jquery==4.1 + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-mermaid==0.9.2 + # via ml-ops-quickstart +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via sphinx +sphinxext-opengraph==0.9.1 + # via ml-ops-quickstart +sqlalchemy==2.0.35 + # via jupyter-cache +stack-data==0.6.3 + # via ipython +tabulate==0.9.0 + # via jupyter-cache +text-unidecode==1.3 + # via python-slugify +tomli==2.0.2 ; python_full_version <= '3.11' + # via coverage + # via mypy + # via pytest + # via sphinx + # via sphinx-autodoc2 +tornado==6.4.1 + # via ipykernel + # via jupyter-client +traitlets==5.14.3 + # via comm + # via ipykernel + # via ipython + # via jupyter-client + # via jupyter-core + # via matplotlib-inline + # via nbclient + # via nbformat +types-python-dateutil==2.9.0.20240906 + # via arrow +typing-extensions==4.12.2 + # via astroid + # via ipython + # via mypy + # via myst-nb + # via pydata-sphinx-theme + # via rich + # via sphinx-autodoc2 + # via sqlalchemy +uc-micro-py==1.0.3 + # via linkify-it-py +urllib3==2.2.3 + # via requests +wcwidth==0.2.13 + # via prompt-toolkit +wheel==0.44.0 + # via sphinx-togglebutton +xxhash==3.5.0 + # via flogging +zipp==3.20.2 + # via importlib-metadata diff --git a/templates/mlops b/templates/mlops new file mode 160000 index 00000000..b6832deb --- /dev/null +++ b/templates/mlops @@ -0,0 +1 @@ +Subproject commit b6832debbf66e41423b0221647b2c32054d86d7e From 1693c3461a3e6c51dd5b403b4d14d053bbf37032 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Wed, 2 Oct 2024 13:14:36 +0200 Subject: [PATCH 06/12] Remove pre-commit from requirements Signed-off-by: guillemdb --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bf882c25..58a3eee7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -304,7 +304,7 @@ omit = ["tests/*"] [tool.hatch.envs.default] installer = "uv" python = "3.10" -post-install-commands = ["pre-commit install"] +post-install-commands = [] #"pre-commit install"] dependencies = [] # Test environment with test-only dependencies [tool.hatch.envs.test] From ccc26e92a3300c0ec7eeaccc08a30122b25b5934 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Sat, 5 Oct 2024 11:09:34 +0200 Subject: [PATCH 07/12] Update template and remove unused features Signed-off-by: guillemdb --- ...mit-config.yaml => .pre-commit-config.yaml | 10 +------ README.md | 5 ++++ __pytest.ini | 30 ------------------- templates/mlops | 2 +- 4 files changed, 7 insertions(+), 40 deletions(-) rename ._pre-commit-config.yaml => .pre-commit-config.yaml (54%) delete mode 100644 __pytest.ini diff --git a/._pre-commit-config.yaml b/.pre-commit-config.yaml similarity index 54% rename from ._pre-commit-config.yaml rename to .pre-commit-config.yaml index 0da324c5..cc5fe43d 100644 --- a/._pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,18 +2,10 @@ # pre-commit install --install-hooks # To update the versions: # pre-commit autoupdate -exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg)(/|$)' # Note the order is intentional to avoid multiple passes of the hooks repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: main - hooks: - - id: ruff - args: [--fix, --exit-non-zero-on-fix, --show-fixes] - - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks - rev: main + rev: v4.6.0 hooks: - id: trailing-whitespace - - id: end-of-file-fixer - id: debug-statements diff --git a/README.md b/README.md index 22a60844..12bb30eb 100644 --- a/README.md +++ b/README.md @@ -95,3 +95,8 @@ Note, to combine the coverage data from all the tox environments run: - :: PYTEST_ADDOPTS=--cov-append tox + + +I need you to re-write the following document into markdown. Given that I need to copy-paste it as a raw document, please do the following: +- Write the answer inside a single ```makdown ``` block. +- Inside that block please replace any \`\`\` with \'\'\' diff --git a/__pytest.ini b/__pytest.ini deleted file mode 100644 index ddbac936..00000000 --- a/__pytest.ini +++ /dev/null @@ -1,30 +0,0 @@ -[pytest] -# If a pytest section is found in one of the possible config files -# (pytest.ini, tox.ini or setup.cfg), then pytest will not look for any others, -# so if you add a pytest config section elsewhere, -# you will need to delete this section from setup.cfg. -norecursedirs = - migrations - -python_files = - test_*.py - *_test.py - tests.py -addopts = - -ra - --strict-markers - --doctest-modules - --doctest-glob=\*.rst - --tb=short -testpaths = - tests -# If you want to switch back to tests outside package just remove --pyargs -# and edit testpaths to have "tests/" instead of "mloq". - -# Idea from: https://til.simonwillison.net/pytest/treat-warnings-as-errors -filterwarnings = - error -# You can add exclusions, some examples: -# ignore:'mloq' defines default_app_config:PendingDeprecationWarning:: -# ignore:The {{% if::: -# ignore:Coverage disabled via --no-cov switch! diff --git a/templates/mlops b/templates/mlops index b6832deb..93d59e22 160000 --- a/templates/mlops +++ b/templates/mlops @@ -1 +1 @@ -Subproject commit b6832debbf66e41423b0221647b2c32054d86d7e +Subproject commit 93d59e222ad2031a3fe0171c8a3013bab0cd5505 From 7e2631069a4b80ed47f774d904adb7b599d3dc85 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Sun, 6 Oct 2024 08:30:24 +0200 Subject: [PATCH 08/12] Re-generate files using the updated template Signed-off-by: guillemdb --- .bumpversion.cfg | 28 +- .cookiecutterrc | 12 - .gitmodules | 6 - .pre-commit-config.yaml | 2 +- .readthedocs.yml | 2 + AUTHORS.rst => AUTHORS.md | 4 +- CHANGELOG.rst => CHANGELOG.md | 5 +- CONTRIBUTING.md | 75 +++ CONTRIBUTING.rst | 85 ---- MANIFEST.in | 10 +- README.md | 415 +++++++++++++--- docs/_config.yml | 5 +- docs/_toc.yml | 20 +- docs/index.md | 3 +- .../git_book}/markdown-notebooks.md | 0 docs/{ => source/git_book}/markdown.md | 0 docs/{ => source/git_book}/notebooks.ipynb | 0 mloq-template | 2 +- pyproject.toml | 15 +- requirements-dev.lock | 469 ------------------ requirements.lock | 389 --------------- templates/mlops | 2 +- 22 files changed, 469 insertions(+), 1080 deletions(-) delete mode 100644 .gitmodules rename AUTHORS.rst => AUTHORS.md (71%) rename CHANGELOG.rst => CHANGELOG.md (54%) create mode 100644 CONTRIBUTING.md delete mode 100644 CONTRIBUTING.rst rename docs/{ => source/git_book}/markdown-notebooks.md (100%) rename docs/{ => source/git_book}/markdown.md (100%) rename docs/{ => source/git_book}/notebooks.ipynb (100%) delete mode 100644 requirements-dev.lock delete mode 100644 requirements.lock diff --git a/.bumpversion.cfg b/.bumpversion.cfg index b2ebcc80..0e617d4e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,29 +1,3 @@ [bumpversion] current_version = 0.1.0 -;commit = True -;tag = True -; -; -; -;[bumpversion:file:docs/_config.yml] -; -; -;[bumpversion:file:src/mloq/version.py] -; -;search = __version__ = "{current_version}" -;replace = __version__ = "{new_version}" -; -; -;[bumpversion:file:.cookiecutterrc] -;search = version: "{current_version}" -;replace = version: "{new_version}" -; -; -;[bumpversion:file (badge):README.md] -;search = /v{current_version}.svg -;replace = /v{new_version}.svg -; -;[bumpversion:file (link):README.md] -;search = /v{current_version}...main -;replace = /v{new_version}...main -; + diff --git a/.cookiecutterrc b/.cookiecutterrc index 5ddd3ea3..af16dc30 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -15,14 +15,9 @@ # cookiecutter --overwrite-if-exists --config-file=ml-ops-quickstart/.cookiecutterrc gh:ionelmc/cookiecutter-pylibrary default_context: - c_extension_support: "no" - codacy: "no" - codacy_projectid: "[Get ID from https://app.codacy.com/gh/FragileTech/ml-ops-quickstart/settings]" - codeclimate: "no" codecov: "yes" command_line_interface: "click" command_line_interface_bin_name: "mloq" - coveralls: "no" distribution_name: "mloq" docstring_code_line_length: "99" email: "guillem@fragile.tech" @@ -50,17 +45,10 @@ default_context: repo_name: "ml-ops-quickstart" repo_url: "https://github.com/FragileTech/ml-ops-quickstart" repo_username: "FragileTech" - scrutinizer: "no" - setup_py_uses_setuptools_scm: "no" sphinx_docs: "yes" sphinx_docs_hosting: "https://ml-ops-quickstart.readthedocs.io/" - sphinx_doctest: "no" - sphinx_theme: "furo" target_python_version: "3.10" - test_matrix_separate_coverage: "no" - tests_inside_package: "no" version: "0.1.0" - version_manager: "bump2version" website: "fragile.tech" year_from: "2024" year_to: "2024" diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f281a01d..00000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "templates/mlops"] - path = templates/mlops - url = https://github.com/FragileTech/mloq-cookiecutter.git -[submodule "mloq-template"] - path = mloq-template - url = https://github.com/FragileTech/mloq-template.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc5fe43d..41425269 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ # Note the order is intentional to avoid multiple passes of the hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: debug-statements diff --git a/.readthedocs.yml b/.readthedocs.yml index 009a913c..4eece145 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -7,6 +7,8 @@ build: os: ubuntu-22.04 tools: python: "3" + apt_packages: + - texlive-full python: install: - requirements: docs/requirements.txt diff --git a/AUTHORS.rst b/AUTHORS.md similarity index 71% rename from AUTHORS.rst rename to AUTHORS.md index ae7b5f66..537dea30 100644 --- a/AUTHORS.rst +++ b/AUTHORS.md @@ -1,5 +1,3 @@ - -Authors -======= +# Authors * Guillem Duran Ballester - fragile.tech diff --git a/CHANGELOG.rst b/CHANGELOG.md similarity index 54% rename from CHANGELOG.rst rename to CHANGELOG.md index a54242ab..fe5f7be2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.md @@ -1,8 +1,7 @@ +# Changelog -Changelog -========= -0.1.0 (2024-10-02) +0.1.0 (2024-10-06) ------------------ * First release on PyPI. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a2da7ad3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,75 @@ +# Contributing + +Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. + +```{contents} +``` + +## Bug reports + +When [reporting a bug](https://github.com/FragileTech/ml-ops-quickstart/issues) please include: + +- Your operating system name and version. +- Any details about your local setup that might be helpful in troubleshooting. +- Detailed steps to reproduce the bug. + +## Documentation improvements + +ML Ops Quickstart could always use more documentation, whether as part of the official ML Ops Quickstart docs, in docstrings, or even on the web in blog posts, articles, and such. + +## Feature requests and feedback + +The best way to send feedback is to file an issue at [https://github.com/FragileTech/ml-ops-quickstart/issues](https://github.com/FragileTech/ml-ops-quickstart/issues). + +If you are proposing a feature: + +- Explain in detail how it would work. +- Keep the scope as narrow as possible, to make it easier to implement. +- Remember that this is a volunteer-driven project, and that code contributions are welcome :) + +## Development + +To set up `ml-ops-quickstart` for local development: + +1. Fork [ml-ops-quickstart](https://github.com/FragileTech/ml-ops-quickstart) (look for the "Fork" button). +2. Clone your fork locally: + + ```bash + git clone git@github.com:YOURGITHUBNAME/ml-ops-quickstart.git + ``` + +3. Create a branch for local development: + + ```bash + git checkout -b name-of-your-bugfix-or-feature + ``` + + Now you can make your changes locally. + +4. When you're done making changes run all the checks and docs builder with one command: + + ```bash + rye run all + ``` + +5. Commit your changes and push your branch to GitHub: + + ```bash + git add . + git commit -m "Your detailed description of your changes." + git push origin name-of-your-bugfix-or-feature + ``` + +6. Submit a pull request through the GitHub website. + +## Pull Request Guidelines + +If you need some code review or feedback while you're developing the code just make the pull request. + +For merging, you should: + +1. Include passing tests (run `hatch test`). +2. Update documentation when there's new API, functionality etc. +3. Add a note to `CHANGELOG.md` about the changes. +4. Add yourself to `AUTHORS.md`. + diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index e4c8094a..00000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,85 +0,0 @@ -============ -Contributing -============ - -Contributions are welcome, and they are greatly appreciated! Every -little bit helps, and credit will always be given. - -Bug reports -=========== - -When `reporting a bug `_ please include: - - * Your operating system name and version. - * Any details about your local setup that might be helpful in troubleshooting. - * Detailed steps to reproduce the bug. - -Documentation improvements -========================== - -ML Ops Quickstart could always use more documentation, whether as part of the -official ML Ops Quickstart docs, in docstrings, or even on the web in blog posts, -articles, and such. - -Feature requests and feedback -============================= - -The best way to send feedback is to file an issue at https://github.com/FragileTech/ml-ops-quickstart/issues. - -If you are proposing a feature: - -* Explain in detail how it would work. -* Keep the scope as narrow as possible, to make it easier to implement. -* Remember that this is a volunteer-driven project, and that code contributions are welcome :) - -Development -=========== - -To set up `ml-ops-quickstart` for local development: - -1. Fork `ml-ops-quickstart `_ - (look for the "Fork" button). -2. Clone your fork locally:: - - git clone git@github.com:YOURGITHUBNAME/ml-ops-quickstart.git - -3. Create a branch for local development:: - - git checkout -b name-of-your-bugfix-or-feature - - Now you can make your changes locally. - -4. When you're done making changes run all the checks and docs builder with one command:: - - tox - -5. Commit your changes and push your branch to GitHub:: - - git add . - git commit -m "Your detailed description of your changes." - git push origin name-of-your-bugfix-or-feature - -6. Submit a pull request through the GitHub website. - -Pull Request Guidelines ------------------------ - -If you need some code review or feedback while you're developing the code just make the pull request. - -For merging, you should: - -1. Include passing tests (run ``hatch``). -2. Update documentation when there's new API, functionality etc. -3. Add a note to ``CHANGELOG.rst`` about the changes. -4. Add yourself to ``AUTHORS.rst``. - -Tips ----- - -To run a subset of tests:: - - tox -e envname -- pytest -k test_myfeature - -To run all the test environments in *parallel*:: - - tox -p auto diff --git a/MANIFEST.in b/MANIFEST.in index d0dac9c3..06e8bdb1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,13 +10,13 @@ include .editorconfig include .github/workflows/github-actions.yml include .pre-commit-config.yaml include .readthedocs.yml +include pyproject.toml include pytest.ini -include tox.ini -include AUTHORS.rst -include CHANGELOG.rst -include CONTRIBUTING.rst +include AUTHORS.md +include CHANGELOG.md +include CONTRIBUTING.md include LICENSE -include README.rst +include README.md global-exclude *.py[cod] __pycache__/* *.so *.dylib diff --git a/README.md b/README.md index 12bb30eb..2de89179 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,385 @@ -======== -Overview -======== +# Welcome to ML Ops Quickstart -.. start-badges +Automate project creation following ML best practices. -.. list-table:: - :stub-columns: 1 - * - docs - - |docs| - * - tests - - |github-actions| |codecov| - * - package - - |version| |wheel| |supported-versions| |supported-implementations| |commits-since| -.. |docs| image:: https://readthedocs.org/projects/ml-ops-quickstart/badge/?style=flat - :target: https://readthedocs.org/projects/ml-ops-quickstart/ - :alt: Documentation Status +* License: MIT license -.. |github-actions| image:: https://github.com/FragileTech/ml-ops-quickstart/actions/workflows/github-actions.yml/badge.svg - :alt: GitHub Actions Build Status - :target: https://github.com/FragileTech/ml-ops-quickstart/actions -.. |codecov| image:: https://codecov.io/gh/FragileTech/ml-ops-quickstart/branch/main/graphs/badge.svg?branch=main - :alt: Coverage Status - :target: https://app.codecov.io/github/FragileTech/ml-ops-quickstart +## Features -.. |version| image:: https://img.shields.io/pypi/v/mloq.svg - :alt: PyPI Package latest release - :target: https://pypi.org/project/mloq +This is an "all inclusive" sort of template. -.. |wheel| image:: https://img.shields.io/pypi/wheel/mloq.svg - :alt: PyPI Wheel - :target: https://pypi.org/project/mloq +- Choice of various licenses. +- [Pytest](http://pytest.org/) for testing Python 3.10. +- *Optional* support for creating a tests matrix out of dependencies and Python versions. -.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/mloq.svg - :alt: Supported versions - :target: https://pypi.org/project/mloq +- [Codecov](http://codecov.io/) for coverage tracking. -.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/mloq.svg - :alt: Supported implementations - :target: https://pypi.org/project/mloq -.. |commits-since| image:: https://img.shields.io/github/commits-since/FragileTech/ml-ops-quickstart/v0.1.0.svg - :alt: Commits since latest release - :target: https://github.com/FragileTech/ml-ops-quickstart/compare/v0.1.0...main +- Documentation with [Sphinx](http://sphinx-doc.org/), ready for [ReadTheDocs](https://readthedocs.org/). +- Configurations for: + - [isort](https://pypi.org/project/isort) + - [bumpversion](https://pypi.org/project/bump2version) ([bump2version](https://github.com/c4urself/bump2version) required) + - [ruff](https://docs.astral.sh/ruff/) for linting and formatting your code. +- Packaging and code quality checks. This template comes with a Hatch environment (`check`) that will: + - Check if your `README.md` is valid. + - Check if the `MANIFEST.in` has any issues. +## Requirements -.. end-badges +Projects using this template have these minimal dependencies: -Automate project creation following ML best practices. +- [Cookiecutter](https://github.com/audreyr/cookiecutter) - just for creating the project. +- [Setuptools](https://pypi.org/project/setuptools) - for building the package, wheels, etc. Nowadays Setuptools is widely available, it shouldn't pose a problem :) + +To get quickly started on a new system, just [install setuptools](https://pypi.org/project/setuptools#installation-instructions) and then [install pip](https://pip.pypa.io/en/latest/installing.html). That's the bare minimum required to install Hatch and Cookiecutter. To install them, just run this in your shell or command prompt: + +```bash +pip install cookiecutter +``` + +## Usage and options + +This template is more involved than the regular [cookiecutter-pypackage](https://github.com/audreyr/cookiecutter-pypackage). + +First generate your project: + +```bash +cookiecutter gh:FragileTech/ml-ops-quickstart +``` + +You will be asked for these fields: + +> **Note**: Fields that work together usually use the same prefix. If you answer "no" on the first one, then the rest won't have any effect so just ignore them. Maybe in the future Cookiecutter will allow option hiding or something like a wizard. + +### `full_name` + +**Default**: + +```python +"Guillem Duran Ballester" +``` + +Main author of this library or application (used in `AUTHORS.md` and `pyproject.toml`). + +Can be set in your `~/.cookiecutterrc` config file. + +### `email` + +**Default**: + +```python +"guillem@fragile.tech" +``` + +Contact email of the author (used in `AUTHORS.md` and `pyproject.toml`). + +Can be set in your `~/.cookiecutterrc` config file. + +### `website` + +**Default**: + +```python +"fragile.tech" +``` + +Website of the author (used in `AUTHORS.md`). + +Can be set in your `~/.cookiecutterrc` config file. + +### `repo_username` + +**Default**: + +```python +"FragileTech" +``` + +Repository username of this project (used for repository link). + +Can be set in your `~/.cookiecutterrc` config file. + +### `project_name` + +**Default**: + +```python +"ML Ops Quickstart" +``` + +Verbose project name, used in headings (docs, README, etc). + +### `repo_hosting_domain` + +**Default**: + +```python +"github.com" +``` + +Use `"no"` for no hosting (various links will disappear). You can also use `"gitlab.com"` and such, but various things will be broken. + +### `repo_name` + +**Default**: + +```python +"ml-ops-quickstart" +``` + +Repository name on hosting service (and project's root directory name). + +### `package_name` + +**Default**: + +```python +"mloq" +``` + +Python package name (whatever you would import). + +### `distribution_name` + +**Default**: + +```python +"mloq" +``` + +PyPI distribution name (what you would `pip install`). + +### `module_name` + +**Default**: + +```python +"core" +``` + +This template assumes there's going to be an "implementation" module inside your package. + +### `project_short_description` + +**Default**: + +```python +"Automate project creation following ML best practices." +``` + +One-line description of the project (used in `README.md` and `pyproject.toml`). + +### `release_date` + +**Default**: + +```python +"today" +``` + +Release date of the project (ISO 8601 format), defaults to today (used in `CHANGELOG.md`). + +### `year_from` + +**Default**: + +```python +"2024" +``` + +Copyright start year. + +### `year_to` + +**Default**: + +```python +"2024" +``` + +Copyright end year. + +### `version` + +**Default**: + +```python +"0.1.0" +``` + +Release version (see `.bumpversion.cfg` and in Sphinx `conf.py`). + +### `command_line_interface` + +**Default**: + +```python +"click" +``` + +Option to enable a CLI (a bin/executable file). Available options: + +- `plain` - a very simple command. +- `argparse` - a command implemented with `argparse`. +- `click` - a command implemented with [click](http://click.pocoo.org/) - which you can use to build more complex commands. +- `no` - no CLI at all. + +### `command_line_interface_bin_name` + +**Default**: + +```python +"mloq" +``` + +Name of the CLI bin/executable file (set the console script name in `pyproject.toml`). + +### `license` + +**Default**: + +```python +"MIT license" +``` + +License to use. Available options: + +- MIT license +- BSD 2-Clause License +- BSD 3-Clause License +- ISC license +- Apache Software License 2.0 +- GNU Lesser General Public License v3 or later (LGPLv3+) +- GNU Lesser General Public License v3 (LGPLv3) +- GNU Lesser General Public License v2.1 or later (LGPLv2+) +- GNU Lesser General Public License v2.1 (LGPLv2) +- no + +What license to pick? https://choosealicense.com/ + +### `codecov` + +**Default**: + +```python +"yes" +``` + +Enable pushing coverage data to Codecov and add badge in `README.md`. + +### `sphinx_docs` + +**Default**: + +```python +"yes" +``` + +Have Sphinx documentation. + +### `sphinx_docs_hosting` + +**Default**: + +```python +"https://ml-ops-quickstart.readthedocs.io/" +``` + +Leave as default if your documentation will be hosted on ReadTheDocs. If your documentation will be hosted elsewhere (such as GitHub Pages or GitLab Pages), enter the top-level URL. + +### `pypi_badge` + +**Default**: + +```python +"yes" +``` + +By default, this will insert links to your project's page on PyPI.org. If you choose `"no"`, then these links will not be created. + +### `pypi_disable_upload` + +**Default**: + +```python +"no" +``` + +If you specifically want to be sure your package will never be accidentally uploaded to PyPI, you can pick `"yes"`. + +## Developing the project + +To format and lint the code: + +```bash +rye run style +``` + +To run all the tests, just run: + +```bash +rye run test +``` + +To see all the Hatch environments: + +```bash +rye run hatch env show +``` + +To only build the docs: + +```bash +rye run build-docs +``` -* Free software: MIT license +To build and verify that the built package is proper and perform other code QA checks: -Installation -============ +```bash +rye run check +``` -:: +## Releasing the project - pip install mloq +Before releasing your package on PyPI, you should have all the tests in the different environments passing. -You can also install the in-development version with:: +### Version management - pip install https://github.com/FragileTech/ml-ops-quickstart/archive/main.zip +This template provides a basic bumpversion configuration. It's as simple as running: +- `bumpversion patch` to increase version from `1.0.0` to `1.0.1`. +- `bumpversion minor` to increase version from `1.0.0` to `1.1.0`. +- `bumpversion major` to increase version from `1.0.0` to `2.0.0`. -Documentation -============= +You should read [Semantic Versioning 2.0.0](http://semver.org/) before bumping versions. +### Building and uploading -https://ml-ops-quickstart.readthedocs.io/ +TODO +## Changelog -Development -=========== +See [CHANGELOG.md](https://github.com/FragileTech/ml-ops-quickstart/blob/main/CHANGELOG.md). -To run all the tests run:: +## Questions & answers - tox +**There's no Makefile?** -Note, to combine the coverage data from all the tox environments run: +Sorry, no `Makefile` yet. The Hatch environments stand for whatever you'd have in a `Makefile`. -.. list-table:: - :widths: 10 90 - :stub-columns: 1 +**Why is the version stored in several files (`pkg/__init__.py`, `pyproject.toml`, `docs/conf.py`)?** - - - Windows - - :: +We cannot use a metadata/version file[^1] because this template is to be used with both distributions of packages (dirs with `__init__.py`) and modules (simple `.py` files that go straight into `site-packages`). There's no good place for that extra file if you're distributing modules. - set PYTEST_ADDOPTS=--cov-append - tox +But this isn't so bad—bumpversion manages the version string quite neatly. - - - Other - - :: +[^1]: Example, an `__about__.py` file. - PYTEST_ADDOPTS=--cov-append tox +## Not Exactly What You Want? +No way, this is the best. 😜 -I need you to re-write the following document into markdown. Given that I need to copy-paste it as a raw document, please do the following: -- Write the answer inside a single ```makdown ``` block. -- Inside that block please replace any \`\`\` with \'\'\' +If you have criticism or suggestions, please open up an Issue or Pull Request. diff --git a/docs/_config.yml b/docs/_config.yml index 65fb5615..d8228b3a 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -110,5 +110,6 @@ sphinx: napoleon_numpy_docstring : false autodoc_typehints : "description" autoapi_add_toctree_entry : true - version : 0.1.0 # The version of the book - release : 0.1.0 # The release of the book + imgmath_latex : /usr/bin/latex + version : "0.1.0" # The version of the book + release : "0.1.0" # The release of the book diff --git a/docs/_toc.yml b/docs/_toc.yml index 421aed2f..da3ff906 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -3,7 +3,19 @@ format: jb-book root: index -chapters: -- file: markdown -- file: notebooks -- file: markdown-notebooks +title: Welcome to ML Ops Quickstart +defaults: + maxdepth: 4 +parts: + - caption: About ML Ops Quickstart + maxdepth: 3 + chapters: + - file: source/project/CONTRIBUTING + - file: source/project/CHANGELOG + - file: source/project/AUTHORS + - caption: Sphinx Book Theme +# numbered: True # Only applies to chapters in Part 1. + chapters: + - file: source/git_book/markdown + - file: source/git_book/notebooks + - file: source/git_book/markdown-notebooks diff --git a/docs/index.md b/docs/index.md index 2983e227..6da2c4fc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,5 @@ -# Welcome to ML Ops Quickstart Jupyter Book +```{include} source/project/README.md +``` This is the main documentation page for **ML Ops Quickstart**. diff --git a/docs/markdown-notebooks.md b/docs/source/git_book/markdown-notebooks.md similarity index 100% rename from docs/markdown-notebooks.md rename to docs/source/git_book/markdown-notebooks.md diff --git a/docs/markdown.md b/docs/source/git_book/markdown.md similarity index 100% rename from docs/markdown.md rename to docs/source/git_book/markdown.md diff --git a/docs/notebooks.ipynb b/docs/source/git_book/notebooks.ipynb similarity index 100% rename from docs/notebooks.ipynb rename to docs/source/git_book/notebooks.ipynb diff --git a/mloq-template b/mloq-template index 0dd780b4..f13ae31b 160000 --- a/mloq-template +++ b/mloq-template @@ -1 +1 @@ -Subproject commit 0dd780b40352f8080c214070f53b83e13654353a +Subproject commit f13ae31b88554df2ec8fd182fe41b3df55337524 diff --git a/pyproject.toml b/pyproject.toml index 58a3eee7..72b5d690 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,8 +44,8 @@ dynamic = ["version"] # direct dependencies of this package, installed when users `pip install ml-ops-quickstart` later. dependencies = [ "flogging", - "hydraclick", - "cookiecutter", + "pre-commit", + "click", ] # ToDo: Modify according to your needs! [project.urls] # important URLs for this project @@ -110,6 +110,7 @@ docs = { cmd = "hatch run docs:docs" } build-docs = { cmd = "hatch run docs:build"} sphinx = { cmd = "hatch run docs:sphinx" } serve-docs = { cmd = "hatch run docs:serve" } +all = {chain= ["lint", "build-docs", "test"]} [tool.ruff] # Assume Python 3.10 @@ -260,7 +261,7 @@ enable = """, # To disable a specific warning --> action:message:category:module:line filterwarnings = ["ignore::UserWarning", 'ignore::DeprecationWarning'] addopts = "--ignore=scripts --doctest-continue-on-failure" - +python_files = ["test_*.py", "tests.py", "test.py"] # Code coverage config [tool.coverage.run] branch = true @@ -304,7 +305,7 @@ omit = ["tests/*"] [tool.hatch.envs.default] installer = "uv" python = "3.10" -post-install-commands = [] #"pre-commit install"] +post-install-commands = ["pre-commit install"] dependencies = [] # Test environment with test-only dependencies [tool.hatch.envs.test] @@ -312,7 +313,7 @@ description = "Run tests and coverage" features = ["test"] [tool.hatch.envs.test.scripts] run_pytest = "pytest -s -o log_cli=true -o log_cli_level=info {args}" -doctest = "run_pytest --doctest-modules -n 0 {args:src}" +doctest = "run_pytest --doctest-modules --doctest-glob=\\*.md -n 0 {args:src}" test = ["doctest", "run_pytest {args:tests}"] cov = "run_pytest -n auto --cov-report=term-missing --cov-config=pyproject.toml --cov=src/mloq --cov=tests {args}" no-cov = "cov --no-cov {args}" @@ -329,6 +330,10 @@ PYTHONUNBUFFERED = "1" [tool.hatch.envs.docs.scripts] features = ["docs"] build = [ + "cp AUTHORS.md docs/source/project/", + "cp CHANGELOG.md docs/source/project/", + "cp CONTRIBUTING.md docs/source/project/", + "cp README.md docs/source/project/", "jupyter-book build docs/ ", "uv pip freeze > docs/requirements.txt", ] diff --git a/requirements-dev.lock b/requirements-dev.lock deleted file mode 100644 index 27cff1dc..00000000 --- a/requirements-dev.lock +++ /dev/null @@ -1,469 +0,0 @@ -# generated by rye -# use `rye lock` or `rye sync` to update this lockfile -# -# last locked with the following flags: -# pre: false -# features: [] -# all-features: true -# with-sources: false -# generate-hashes: false -# universal: true - --e file:. -accessible-pygments==0.0.5 - # via pydata-sphinx-theme -alabaster==0.7.16 - # via sphinx -antlr4-python3-runtime==4.9.3 - # via hydra-core - # via omegaconf -anyio==4.6.0 - # via httpx -appnope==0.1.4 ; platform_system == 'Darwin' - # via ipykernel -arrow==1.3.0 - # via cookiecutter -astroid==3.3.4 - # via sphinx-autoapi - # via sphinx-autodoc2 -asttokens==2.4.1 - # via stack-data -attrs==24.2.0 - # via jsonschema - # via jupyter-cache - # via referencing -autoapi==2.0.1 - # via ml-ops-quickstart -babel==2.16.0 - # via pydata-sphinx-theme - # via sphinx -backports-tarfile==1.2.0 ; python_full_version < '3.12' - # via jaraco-context -beautifulsoup4==4.12.3 - # via pydata-sphinx-theme -binaryornot==0.4.4 - # via cookiecutter -certifi==2024.8.30 - # via httpcore - # via httpx - # via requests -cffi==1.17.1 ; (platform_python_implementation != 'PyPy' and sys_platform == 'linux') or platform_python_implementation == 'PyPy' or implementation_name == 'pypy' - # via cryptography - # via pyzmq - # via zstandard -chardet==5.2.0 - # via binaryornot -charset-normalizer==3.3.2 - # via requests -click==8.1.7 - # via cookiecutter - # via hatch - # via hydraclick - # via jupyter-book - # via jupyter-cache - # via sphinx-external-toc - # via userpath -colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows' - # via click - # via ipython - # via pytest - # via sphinx -comm==0.2.2 - # via ipykernel -cookiecutter==2.6.0 - # via ml-ops-quickstart -coverage==7.6.1 - # via pytest-cov -cryptography==43.0.1 ; sys_platform == 'linux' - # via secretstorage -debugpy==1.8.6 - # via ipykernel -decorator==5.1.1 - # via ipython -distlib==0.3.8 - # via virtualenv -distro==1.9.0 - # via ruyaml -docutils==0.20.1 - # via myst-parser - # via pybtex-docutils - # via pydata-sphinx-theme - # via sphinx - # via sphinx-rtd-theme - # via sphinx-togglebutton - # via sphinxcontrib-bibtex -exceptiongroup==1.2.2 ; python_full_version < '3.11' - # via anyio - # via ipython - # via pytest -execnet==2.1.1 - # via pytest-xdist -executing==2.1.0 - # via stack-data -fastjsonschema==2.20.0 - # via nbformat -filelock==3.16.1 - # via virtualenv -flogging==0.0.23 - # via ml-ops-quickstart -greenlet==3.1.1 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64') - # via sqlalchemy -h11==0.14.0 - # via httpcore -hatch==1.12.0 -hatchling==1.25.0 - # via hatch -httpcore==1.0.6 - # via httpx -httpx==0.27.2 - # via hatch -hydra-core==1.3.2 - # via hydraclick -hydraclick==0.0.8 - # via ml-ops-quickstart -hyperlink==21.0.0 - # via hatch -idna==3.10 - # via anyio - # via httpx - # via hyperlink - # via requests -imagesize==1.4.1 - # via sphinx -importlib-metadata==8.5.0 - # via jupyter-cache - # via keyring - # via myst-nb -iniconfig==2.0.0 - # via pytest -ipykernel==6.29.5 - # via myst-nb -ipython==8.27.0 - # via ipykernel - # via myst-nb -jaraco-classes==3.4.0 - # via keyring -jaraco-context==6.0.1 - # via keyring -jaraco-functools==4.1.0 - # via keyring -jedi==0.19.1 - # via ipython -jeepney==0.8.0 ; sys_platform == 'linux' - # via keyring - # via secretstorage -jinja2==3.1.4 - # via cookiecutter - # via jupyter-book - # via myst-parser - # via sphinx - # via sphinx-autoapi -jsonschema==4.23.0 - # via jupyter-book - # via nbformat -jsonschema-specifications==2023.12.1 - # via jsonschema -jupyter-book==1.0.2 - # via ml-ops-quickstart -jupyter-cache==1.0.0 - # via ml-ops-quickstart - # via myst-nb -jupyter-client==8.6.3 - # via ipykernel - # via nbclient -jupyter-core==5.7.2 - # via ipykernel - # via jupyter-client - # via nbclient - # via nbformat -keyring==25.4.1 - # via hatch -latexcodec==3.0.0 - # via pybtex -linkify-it-py==2.0.3 - # via jupyter-book - # via ml-ops-quickstart -markdown-it-py==3.0.0 - # via mdit-py-plugins - # via myst-parser - # via rich -markupsafe==2.1.5 - # via jinja2 -matplotlib-inline==0.1.7 - # via ipykernel - # via ipython -mdit-py-plugins==0.4.2 - # via myst-parser -mdurl==0.1.2 - # via markdown-it-py -more-itertools==10.5.0 - # via jaraco-classes - # via jaraco-functools -mypy==1.11.2 - # via ml-ops-quickstart -mypy-extensions==1.0.0 - # via mypy -myst-nb==1.1.2 - # via jupyter-book - # via ml-ops-quickstart -myst-parser==2.0.0 - # via jupyter-book - # via ml-ops-quickstart - # via myst-nb -nbclient==0.10.0 - # via jupyter-cache - # via myst-nb -nbformat==5.10.4 - # via jupyter-cache - # via myst-nb - # via nbclient -nest-asyncio==1.6.0 - # via ipykernel -omegaconf==2.3.0 - # via hydra-core -packaging==24.1 - # via hatch - # via hatchling - # via hydra-core - # via ipykernel - # via pydata-sphinx-theme - # via pytest - # via sphinx - # via sphinx-jupyterbook-latex -parso==0.8.4 - # via jedi -pathspec==0.12.1 - # via hatchling -pexpect==4.9.0 - # via hatch - # via ipython -platformdirs==4.3.6 - # via hatch - # via jupyter-core - # via virtualenv -pluggy==1.5.0 - # via hatchling - # via pytest -prompt-toolkit==3.0.48 - # via ipython -psutil==6.0.0 - # via ipykernel -ptyprocess==0.7.0 - # via pexpect -pure-eval==0.2.3 - # via stack-data -pybtex==0.24.0 - # via pybtex-docutils - # via sphinxcontrib-bibtex -pybtex-docutils==1.0.3 - # via sphinxcontrib-bibtex -pycparser==2.22 ; (platform_python_implementation != 'PyPy' and sys_platform == 'linux') or platform_python_implementation == 'PyPy' or implementation_name == 'pypy' - # via cffi -pydata-sphinx-theme==0.15.4 - # via ml-ops-quickstart - # via sphinx-book-theme -pygments==2.18.0 - # via accessible-pygments - # via ipython - # via pydata-sphinx-theme - # via rich - # via sphinx -pytest==8.3.3 - # via ml-ops-quickstart - # via pytest-cov - # via pytest-xdist -pytest-cov==5.0.0 - # via ml-ops-quickstart -pytest-xdist==3.6.1 - # via ml-ops-quickstart -python-dateutil==2.9.0.post0 - # via arrow - # via jupyter-client -python-slugify==8.0.4 - # via cookiecutter -pywin32==306 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' - # via jupyter-core -pywin32-ctypes==0.2.3 ; sys_platform == 'win32' - # via keyring -pyyaml==6.0.2 - # via cookiecutter - # via jupyter-book - # via jupyter-cache - # via myst-nb - # via myst-parser - # via omegaconf - # via pybtex - # via sphinx-autoapi - # via sphinx-external-toc -pyzmq==26.2.0 - # via ipykernel - # via jupyter-client -referencing==0.35.1 - # via jsonschema - # via jsonschema-specifications -requests==2.32.3 - # via cookiecutter - # via sphinx -rich==13.9.1 - # via cookiecutter - # via hatch -rpds-py==0.20.0 - # via jsonschema - # via referencing -ruff==0.6.8 - # via ml-ops-quickstart -ruyaml==0.91.0 - # via ml-ops-quickstart -secretstorage==3.3.3 ; sys_platform == 'linux' - # via keyring -setuptools==75.1.0 - # via ruyaml - # via sphinx-togglebutton - # via sphinxcontrib-bibtex -shellingham==1.5.4 - # via hatch -six==1.16.0 - # via asttokens - # via pybtex - # via python-dateutil -sniffio==1.3.1 - # via anyio - # via httpx -snowballstemmer==2.2.0 - # via sphinx -soupsieve==2.6 - # via beautifulsoup4 -sphinx==7.4.7 - # via autoapi - # via jupyter-book - # via ml-ops-quickstart - # via myst-nb - # via myst-parser - # via pydata-sphinx-theme - # via sphinx-autoapi - # via sphinx-book-theme - # via sphinx-comments - # via sphinx-copybutton - # via sphinx-design - # via sphinx-external-toc - # via sphinx-jupyterbook-latex - # via sphinx-multitoc-numbering - # via sphinx-rtd-theme - # via sphinx-thebe - # via sphinx-togglebutton - # via sphinxcontrib-bibtex - # via sphinxcontrib-jquery - # via sphinxext-opengraph -sphinx-autoapi==3.3.2 - # via ml-ops-quickstart -sphinx-autodoc2==0.5.0 - # via ml-ops-quickstart -sphinx-book-theme==1.1.3 - # via jupyter-book - # via ml-ops-quickstart -sphinx-comments==0.0.3 - # via jupyter-book -sphinx-copybutton==0.5.2 - # via jupyter-book - # via ml-ops-quickstart -sphinx-design==0.6.1 - # via jupyter-book -sphinx-external-toc==1.0.1 - # via jupyter-book -sphinx-jupyterbook-latex==1.0.0 - # via jupyter-book -sphinx-multitoc-numbering==0.1.3 - # via jupyter-book -sphinx-rtd-theme==2.0.0 - # via ml-ops-quickstart -sphinx-thebe==0.3.1 - # via jupyter-book -sphinx-togglebutton==0.3.2 - # via jupyter-book - # via ml-ops-quickstart -sphinxcontrib-applehelp==2.0.0 - # via sphinx -sphinxcontrib-bibtex==2.6.3 - # via jupyter-book - # via ml-ops-quickstart -sphinxcontrib-devhelp==2.0.0 - # via sphinx -sphinxcontrib-htmlhelp==2.1.0 - # via sphinx -sphinxcontrib-jquery==4.1 - # via sphinx-rtd-theme -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-mermaid==0.9.2 - # via ml-ops-quickstart -sphinxcontrib-qthelp==2.0.0 - # via sphinx -sphinxcontrib-serializinghtml==2.0.0 - # via sphinx -sphinxext-opengraph==0.9.1 - # via ml-ops-quickstart -sqlalchemy==2.0.35 - # via jupyter-cache -stack-data==0.6.3 - # via ipython -tabulate==0.9.0 - # via jupyter-cache -text-unidecode==1.3 - # via python-slugify -tomli==2.0.2 ; python_full_version <= '3.11' - # via coverage - # via hatchling - # via mypy - # via pytest - # via sphinx - # via sphinx-autodoc2 -tomli-w==1.0.0 - # via hatch -tomlkit==0.13.2 - # via hatch -tornado==6.4.1 - # via ipykernel - # via jupyter-client -traitlets==5.14.3 - # via comm - # via ipykernel - # via ipython - # via jupyter-client - # via jupyter-core - # via matplotlib-inline - # via nbclient - # via nbformat -trove-classifiers==2024.9.12 - # via hatchling -types-python-dateutil==2.9.0.20240906 - # via arrow -typing-extensions==4.12.2 - # via anyio - # via astroid - # via ipython - # via mypy - # via myst-nb - # via pydata-sphinx-theme - # via rich - # via sphinx-autodoc2 - # via sqlalchemy -uc-micro-py==1.0.3 - # via linkify-it-py -urllib3==2.2.3 - # via requests -userpath==1.9.2 - # via hatch -uv==0.4.18 - # via hatch -virtualenv==20.26.6 - # via hatch -wcwidth==0.2.13 - # via prompt-toolkit -wheel==0.44.0 - # via sphinx-togglebutton -xxhash==3.5.0 - # via flogging -zipp==3.20.2 - # via importlib-metadata -zstandard==0.23.0 - # via hatch diff --git a/requirements.lock b/requirements.lock deleted file mode 100644 index 32a743b0..00000000 --- a/requirements.lock +++ /dev/null @@ -1,389 +0,0 @@ -# generated by rye -# use `rye lock` or `rye sync` to update this lockfile -# -# last locked with the following flags: -# pre: false -# features: [] -# all-features: true -# with-sources: false -# generate-hashes: false -# universal: true - --e file:. -accessible-pygments==0.0.5 - # via pydata-sphinx-theme -alabaster==0.7.16 - # via sphinx -antlr4-python3-runtime==4.9.3 - # via hydra-core - # via omegaconf -appnope==0.1.4 ; platform_system == 'Darwin' - # via ipykernel -arrow==1.3.0 - # via cookiecutter -astroid==3.3.4 - # via sphinx-autoapi - # via sphinx-autodoc2 -asttokens==2.4.1 - # via stack-data -attrs==24.2.0 - # via jsonschema - # via jupyter-cache - # via referencing -autoapi==2.0.1 - # via ml-ops-quickstart -babel==2.16.0 - # via pydata-sphinx-theme - # via sphinx -beautifulsoup4==4.12.3 - # via pydata-sphinx-theme -binaryornot==0.4.4 - # via cookiecutter -certifi==2024.8.30 - # via requests -cffi==1.17.1 ; implementation_name == 'pypy' - # via pyzmq -chardet==5.2.0 - # via binaryornot -charset-normalizer==3.3.2 - # via requests -click==8.1.7 - # via cookiecutter - # via hydraclick - # via jupyter-book - # via jupyter-cache - # via sphinx-external-toc -colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows' - # via click - # via ipython - # via pytest - # via sphinx -comm==0.2.2 - # via ipykernel -cookiecutter==2.6.0 - # via ml-ops-quickstart -coverage==7.6.1 - # via pytest-cov -debugpy==1.8.6 - # via ipykernel -decorator==5.1.1 - # via ipython -distro==1.9.0 - # via ruyaml -docutils==0.20.1 - # via myst-parser - # via pybtex-docutils - # via pydata-sphinx-theme - # via sphinx - # via sphinx-rtd-theme - # via sphinx-togglebutton - # via sphinxcontrib-bibtex -exceptiongroup==1.2.2 ; python_full_version < '3.11' - # via ipython - # via pytest -execnet==2.1.1 - # via pytest-xdist -executing==2.1.0 - # via stack-data -fastjsonschema==2.20.0 - # via nbformat -flogging==0.0.23 - # via ml-ops-quickstart -greenlet==3.1.1 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64') - # via sqlalchemy -hydra-core==1.3.2 - # via hydraclick -hydraclick==0.0.8 - # via ml-ops-quickstart -idna==3.10 - # via requests -imagesize==1.4.1 - # via sphinx -importlib-metadata==8.5.0 - # via jupyter-cache - # via myst-nb -iniconfig==2.0.0 - # via pytest -ipykernel==6.29.5 - # via myst-nb -ipython==8.27.0 - # via ipykernel - # via myst-nb -jedi==0.19.1 - # via ipython -jinja2==3.1.4 - # via cookiecutter - # via jupyter-book - # via myst-parser - # via sphinx - # via sphinx-autoapi -jsonschema==4.23.0 - # via jupyter-book - # via nbformat -jsonschema-specifications==2023.12.1 - # via jsonschema -jupyter-book==1.0.2 - # via ml-ops-quickstart -jupyter-cache==1.0.0 - # via ml-ops-quickstart - # via myst-nb -jupyter-client==8.6.3 - # via ipykernel - # via nbclient -jupyter-core==5.7.2 - # via ipykernel - # via jupyter-client - # via nbclient - # via nbformat -latexcodec==3.0.0 - # via pybtex -linkify-it-py==2.0.3 - # via jupyter-book - # via ml-ops-quickstart -markdown-it-py==3.0.0 - # via mdit-py-plugins - # via myst-parser - # via rich -markupsafe==2.1.5 - # via jinja2 -matplotlib-inline==0.1.7 - # via ipykernel - # via ipython -mdit-py-plugins==0.4.2 - # via myst-parser -mdurl==0.1.2 - # via markdown-it-py -mypy==1.11.2 - # via ml-ops-quickstart -mypy-extensions==1.0.0 - # via mypy -myst-nb==1.1.2 - # via jupyter-book - # via ml-ops-quickstart -myst-parser==2.0.0 - # via jupyter-book - # via ml-ops-quickstart - # via myst-nb -nbclient==0.10.0 - # via jupyter-cache - # via myst-nb -nbformat==5.10.4 - # via jupyter-cache - # via myst-nb - # via nbclient -nest-asyncio==1.6.0 - # via ipykernel -omegaconf==2.3.0 - # via hydra-core -packaging==24.1 - # via hydra-core - # via ipykernel - # via pydata-sphinx-theme - # via pytest - # via sphinx - # via sphinx-jupyterbook-latex -parso==0.8.4 - # via jedi -pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' - # via ipython -platformdirs==4.3.6 - # via jupyter-core -pluggy==1.5.0 - # via pytest -prompt-toolkit==3.0.48 - # via ipython -psutil==6.0.0 - # via ipykernel -ptyprocess==0.7.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' - # via pexpect -pure-eval==0.2.3 - # via stack-data -pybtex==0.24.0 - # via pybtex-docutils - # via sphinxcontrib-bibtex -pybtex-docutils==1.0.3 - # via sphinxcontrib-bibtex -pycparser==2.22 ; implementation_name == 'pypy' - # via cffi -pydata-sphinx-theme==0.15.4 - # via ml-ops-quickstart - # via sphinx-book-theme -pygments==2.18.0 - # via accessible-pygments - # via ipython - # via pydata-sphinx-theme - # via rich - # via sphinx -pytest==8.3.3 - # via ml-ops-quickstart - # via pytest-cov - # via pytest-xdist -pytest-cov==5.0.0 - # via ml-ops-quickstart -pytest-xdist==3.6.1 - # via ml-ops-quickstart -python-dateutil==2.9.0.post0 - # via arrow - # via jupyter-client -python-slugify==8.0.4 - # via cookiecutter -pywin32==306 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' - # via jupyter-core -pyyaml==6.0.2 - # via cookiecutter - # via jupyter-book - # via jupyter-cache - # via myst-nb - # via myst-parser - # via omegaconf - # via pybtex - # via sphinx-autoapi - # via sphinx-external-toc -pyzmq==26.2.0 - # via ipykernel - # via jupyter-client -referencing==0.35.1 - # via jsonschema - # via jsonschema-specifications -requests==2.32.3 - # via cookiecutter - # via sphinx -rich==13.9.1 - # via cookiecutter -rpds-py==0.20.0 - # via jsonschema - # via referencing -ruff==0.6.8 - # via ml-ops-quickstart -ruyaml==0.91.0 - # via ml-ops-quickstart -setuptools==75.1.0 - # via ruyaml - # via sphinx-togglebutton - # via sphinxcontrib-bibtex -six==1.16.0 - # via asttokens - # via pybtex - # via python-dateutil -snowballstemmer==2.2.0 - # via sphinx -soupsieve==2.6 - # via beautifulsoup4 -sphinx==7.4.7 - # via autoapi - # via jupyter-book - # via ml-ops-quickstart - # via myst-nb - # via myst-parser - # via pydata-sphinx-theme - # via sphinx-autoapi - # via sphinx-book-theme - # via sphinx-comments - # via sphinx-copybutton - # via sphinx-design - # via sphinx-external-toc - # via sphinx-jupyterbook-latex - # via sphinx-multitoc-numbering - # via sphinx-rtd-theme - # via sphinx-thebe - # via sphinx-togglebutton - # via sphinxcontrib-bibtex - # via sphinxcontrib-jquery - # via sphinxext-opengraph -sphinx-autoapi==3.3.2 - # via ml-ops-quickstart -sphinx-autodoc2==0.5.0 - # via ml-ops-quickstart -sphinx-book-theme==1.1.3 - # via jupyter-book - # via ml-ops-quickstart -sphinx-comments==0.0.3 - # via jupyter-book -sphinx-copybutton==0.5.2 - # via jupyter-book - # via ml-ops-quickstart -sphinx-design==0.6.1 - # via jupyter-book -sphinx-external-toc==1.0.1 - # via jupyter-book -sphinx-jupyterbook-latex==1.0.0 - # via jupyter-book -sphinx-multitoc-numbering==0.1.3 - # via jupyter-book -sphinx-rtd-theme==2.0.0 - # via ml-ops-quickstart -sphinx-thebe==0.3.1 - # via jupyter-book -sphinx-togglebutton==0.3.2 - # via jupyter-book - # via ml-ops-quickstart -sphinxcontrib-applehelp==2.0.0 - # via sphinx -sphinxcontrib-bibtex==2.6.3 - # via jupyter-book - # via ml-ops-quickstart -sphinxcontrib-devhelp==2.0.0 - # via sphinx -sphinxcontrib-htmlhelp==2.1.0 - # via sphinx -sphinxcontrib-jquery==4.1 - # via sphinx-rtd-theme -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-mermaid==0.9.2 - # via ml-ops-quickstart -sphinxcontrib-qthelp==2.0.0 - # via sphinx -sphinxcontrib-serializinghtml==2.0.0 - # via sphinx -sphinxext-opengraph==0.9.1 - # via ml-ops-quickstart -sqlalchemy==2.0.35 - # via jupyter-cache -stack-data==0.6.3 - # via ipython -tabulate==0.9.0 - # via jupyter-cache -text-unidecode==1.3 - # via python-slugify -tomli==2.0.2 ; python_full_version <= '3.11' - # via coverage - # via mypy - # via pytest - # via sphinx - # via sphinx-autodoc2 -tornado==6.4.1 - # via ipykernel - # via jupyter-client -traitlets==5.14.3 - # via comm - # via ipykernel - # via ipython - # via jupyter-client - # via jupyter-core - # via matplotlib-inline - # via nbclient - # via nbformat -types-python-dateutil==2.9.0.20240906 - # via arrow -typing-extensions==4.12.2 - # via astroid - # via ipython - # via mypy - # via myst-nb - # via pydata-sphinx-theme - # via rich - # via sphinx-autodoc2 - # via sqlalchemy -uc-micro-py==1.0.3 - # via linkify-it-py -urllib3==2.2.3 - # via requests -wcwidth==0.2.13 - # via prompt-toolkit -wheel==0.44.0 - # via sphinx-togglebutton -xxhash==3.5.0 - # via flogging -zipp==3.20.2 - # via importlib-metadata diff --git a/templates/mlops b/templates/mlops index 93d59e22..ec8a25de 160000 --- a/templates/mlops +++ b/templates/mlops @@ -1 +1 @@ -Subproject commit 93d59e222ad2031a3fe0171c8a3013bab0cd5505 +Subproject commit ec8a25de84e0b3ebfb464b8957206f5c28c2dc9e From fd14a2e22112c4eec9b3672bda1df5a85362d0a6 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Sun, 6 Oct 2024 08:37:58 +0200 Subject: [PATCH 09/12] Remove default args in mypy run Signed-off-by: guillemdb --- pyproject.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 72b5d690..b64132df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,6 +149,8 @@ exclude = [ "**/proto/**", "data", "config", + "mloq-template", + "templates", ] # Same as Black. line-length = 99 @@ -244,6 +246,10 @@ show_column_numbers = true warn_no_return = false warn_unused_ignores = true +[[tool.mypy.overrides]] +module = "*.templates.*" +ignore_errors = true + [tool.pylint.master] ignore = 'tests' load-plugins =' pylint.extensions.docparams' @@ -367,7 +373,7 @@ style = [ "ruff check --fix-only --unsafe-fixes {args:.}", "ruff format {args:.}", ] check = [ - "ruff check {args:.}", "ruff format --diff {args:.}","mypy {args:.}" , + "ruff check {args:.}", "ruff format --diff {args:.}","mypy {args:}" , ] all = [ "style", From fb563dd48437b6e0c58c6b9ed5d633dbb196b202 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Sun, 6 Oct 2024 18:35:25 +0200 Subject: [PATCH 10/12] Clean pyproject.toml and bump version Signed-off-by: guillemdb --- .github/workflows/build.yml | 2 +- docs/conf.py | 42 +++ docs/requirements.txt | 9 + docs/source/project/AUTHORS.md | 3 + docs/source/project/CHANGELOG.md | 7 + docs/source/project/CONTRIBUTING.md | 75 +++++ docs/source/project/README.md | 385 ++++++++++++++++++++++ pyproject.toml | 59 ++-- requirements-dev.lock | 475 ++++++++++++++++++++++++++++ requirements.lock | 401 +++++++++++++++++++++++ src/mloq/__init__.py | 3 +- src/mloq/__main__.py | 4 + templates/mlops | 2 +- 13 files changed, 1444 insertions(+), 23 deletions(-) create mode 100644 docs/conf.py create mode 100644 docs/source/project/AUTHORS.md create mode 100644 docs/source/project/CHANGELOG.md create mode 100644 docs/source/project/CONTRIBUTING.md create mode 100644 docs/source/project/README.md create mode 100644 requirements-dev.lock create mode 100644 requirements.lock diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 08119c3a..4fef3201 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: env: PROJECT_NAME: mloq PROJECT_DIR: src/mloq - VERSION_FILE: src/mloq/version.py + VERSION_FILE: "src/mloq/version.py .bumpversion.cfg docs/_config.yml" DEFAULT_BRANCH: main BOT_NAME: fragile-bot BOT_EMAIL: bot@fragile.tech diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..46421ecd --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,42 @@ +############################################################################### +# Auto-generated by `jupyter-book config` +# If you wish to continue using _config.yml, make edits to that file and +# re-generate this one. +############################################################################### +author = 'Guillem Duran Ballester' +autoapi_add_toctree_entry = True +autoapi_dirs = ['../src'] +autodoc_typehints = 'description' +comments_config = {'hypothesis': False, 'utterances': False} +copyright = '2024' +exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build'] +extensions = ['sphinx_togglebutton', 'sphinx_copybutton', 'myst_nb', 'jupyter_book', 'sphinx_thebe', 'sphinx_comments', 'sphinx_external_toc', 'sphinx.ext.intersphinx', 'sphinx_design', 'sphinx_book_theme', 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', 'sphinx.ext.autosectionlabel', 'sphinx.ext.autodoc.typehints', 'sphinx.ext.githubpages', 'sphinxcontrib.mermaid', 'autoapi.extension', 'sphinx_jupyterbook_latex', 'sphinx_multitoc_numbering'] +external_toc_exclude_missing = False +external_toc_path = '_toc.yml' +html_baseurl = '' +html_favicon = 'favicon.png' +html_logo = 'logo.png' +html_sourcelink_suffix = '' +html_theme = 'sphinx_book_theme' +html_theme_options = {'search_bar_text': 'Search this book...', 'launch_buttons': {'notebook_interface': 'classic', 'binderhub_url': '', 'jupyterhub_url': '', 'thebe': False, 'colab_url': ''}, 'path_to_docs': '', 'repository_url': 'https://github.com/FragileTech/ml-ops-quickstart', 'repository_branch': 'main', 'extra_footer': '', 'home_page_in_toc': True, 'announcement': '', 'analytics': {'google_analytics_id': ''}, 'use_repository_button': False, 'use_edit_page_button': False, 'use_issues_button': False} +html_title = 'ML Ops Quickstart' +imgmath_latex = '/usr/bin/latex' +latex_engine = 'pdflatex' +myst_enable_extensions = ['amsmath', 'colon_fence', 'deflist', 'dollarmath', 'html_admonition', 'html_image', 'linkify', 'replacements', 'smartquotes', 'substitution', 'tasklist'] +myst_url_schemes = ['mailto', 'http', 'https'] +napoleon_google_docstring = True +napoleon_numpy_docstring = False +nb_execution_allow_errors = False +nb_execution_cache_path = '' +nb_execution_excludepatterns = [] +nb_execution_in_temp = False +nb_execution_mode = 'auto' +nb_execution_timeout = 30 +nb_output_stderr = 'show' +numfig = True +pygments_style = 'sphinx' +release = '0.1.0' +suppress_warnings = ['myst.domains'] +use_jupyterbook_latex = True +use_multitoc_numbering = True +version = '0.1.0' diff --git a/docs/requirements.txt b/docs/requirements.txt index 38e82580..7c3a5646 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,16 +1,20 @@ accessible-pygments==0.0.5 alabaster==0.7.16 +arrow==1.3.0 astroid==3.3.4 asttokens==2.4.1 attrs==24.2.0 autoapi==2.0.1 babel==2.16.0 beautifulsoup4==4.12.3 +binaryornot==0.4.4 certifi==2024.8.30 cfgv==3.4.0 +chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 comm==0.2.2 +cookiecutter==2.6.0 debugpy==1.8.6 decorator==5.1.1 distlib==0.3.8 @@ -30,6 +34,7 @@ ipykernel==6.29.5 ipython==8.27.0 jedi==0.19.1 jinja2==3.1.4 +jinja2-time==0.2.0 jsonschema==4.23.0 jsonschema-specifications==2023.12.1 jupyter-book==1.0.2 @@ -64,10 +69,12 @@ pybtex-docutils==1.0.3 pydata-sphinx-theme==0.15.4 pygments==2.18.0 python-dateutil==2.9.0.post0 +python-slugify==8.0.4 pyyaml==6.0.2 pyzmq==26.2.0 referencing==0.35.1 requests==2.32.3 +rich==13.9.2 rpds-py==0.20.0 ruyaml==0.91.0 setuptools==75.1.0 @@ -100,9 +107,11 @@ sphinxext-opengraph==0.9.1 sqlalchemy==2.0.35 stack-data==0.6.3 tabulate==0.9.0 +text-unidecode==1.3 tomli==2.0.2 tornado==6.4.1 traitlets==5.14.3 +types-python-dateutil==2.9.0.20241003 typing-extensions==4.12.2 uc-micro-py==1.0.3 urllib3==2.2.3 diff --git a/docs/source/project/AUTHORS.md b/docs/source/project/AUTHORS.md new file mode 100644 index 00000000..537dea30 --- /dev/null +++ b/docs/source/project/AUTHORS.md @@ -0,0 +1,3 @@ +# Authors + +* Guillem Duran Ballester - fragile.tech diff --git a/docs/source/project/CHANGELOG.md b/docs/source/project/CHANGELOG.md new file mode 100644 index 00000000..fe5f7be2 --- /dev/null +++ b/docs/source/project/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + + +0.1.0 (2024-10-06) +------------------ + +* First release on PyPI. diff --git a/docs/source/project/CONTRIBUTING.md b/docs/source/project/CONTRIBUTING.md new file mode 100644 index 00000000..a2da7ad3 --- /dev/null +++ b/docs/source/project/CONTRIBUTING.md @@ -0,0 +1,75 @@ +# Contributing + +Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. + +```{contents} +``` + +## Bug reports + +When [reporting a bug](https://github.com/FragileTech/ml-ops-quickstart/issues) please include: + +- Your operating system name and version. +- Any details about your local setup that might be helpful in troubleshooting. +- Detailed steps to reproduce the bug. + +## Documentation improvements + +ML Ops Quickstart could always use more documentation, whether as part of the official ML Ops Quickstart docs, in docstrings, or even on the web in blog posts, articles, and such. + +## Feature requests and feedback + +The best way to send feedback is to file an issue at [https://github.com/FragileTech/ml-ops-quickstart/issues](https://github.com/FragileTech/ml-ops-quickstart/issues). + +If you are proposing a feature: + +- Explain in detail how it would work. +- Keep the scope as narrow as possible, to make it easier to implement. +- Remember that this is a volunteer-driven project, and that code contributions are welcome :) + +## Development + +To set up `ml-ops-quickstart` for local development: + +1. Fork [ml-ops-quickstart](https://github.com/FragileTech/ml-ops-quickstart) (look for the "Fork" button). +2. Clone your fork locally: + + ```bash + git clone git@github.com:YOURGITHUBNAME/ml-ops-quickstart.git + ``` + +3. Create a branch for local development: + + ```bash + git checkout -b name-of-your-bugfix-or-feature + ``` + + Now you can make your changes locally. + +4. When you're done making changes run all the checks and docs builder with one command: + + ```bash + rye run all + ``` + +5. Commit your changes and push your branch to GitHub: + + ```bash + git add . + git commit -m "Your detailed description of your changes." + git push origin name-of-your-bugfix-or-feature + ``` + +6. Submit a pull request through the GitHub website. + +## Pull Request Guidelines + +If you need some code review or feedback while you're developing the code just make the pull request. + +For merging, you should: + +1. Include passing tests (run `hatch test`). +2. Update documentation when there's new API, functionality etc. +3. Add a note to `CHANGELOG.md` about the changes. +4. Add yourself to `AUTHORS.md`. + diff --git a/docs/source/project/README.md b/docs/source/project/README.md new file mode 100644 index 00000000..2de89179 --- /dev/null +++ b/docs/source/project/README.md @@ -0,0 +1,385 @@ +# Welcome to ML Ops Quickstart + +Automate project creation following ML best practices. + + +* License: MIT license + + +## Features + +This is an "all inclusive" sort of template. + +- Choice of various licenses. +- [Pytest](http://pytest.org/) for testing Python 3.10. +- *Optional* support for creating a tests matrix out of dependencies and Python versions. + +- [Codecov](http://codecov.io/) for coverage tracking. + + +- Documentation with [Sphinx](http://sphinx-doc.org/), ready for [ReadTheDocs](https://readthedocs.org/). + +- Configurations for: + - [isort](https://pypi.org/project/isort) + - [bumpversion](https://pypi.org/project/bump2version) ([bump2version](https://github.com/c4urself/bump2version) required) + - [ruff](https://docs.astral.sh/ruff/) for linting and formatting your code. +- Packaging and code quality checks. This template comes with a Hatch environment (`check`) that will: + - Check if your `README.md` is valid. + - Check if the `MANIFEST.in` has any issues. + +## Requirements + +Projects using this template have these minimal dependencies: + +- [Cookiecutter](https://github.com/audreyr/cookiecutter) - just for creating the project. +- [Setuptools](https://pypi.org/project/setuptools) - for building the package, wheels, etc. Nowadays Setuptools is widely available, it shouldn't pose a problem :) + +To get quickly started on a new system, just [install setuptools](https://pypi.org/project/setuptools#installation-instructions) and then [install pip](https://pip.pypa.io/en/latest/installing.html). That's the bare minimum required to install Hatch and Cookiecutter. To install them, just run this in your shell or command prompt: + +```bash +pip install cookiecutter +``` + +## Usage and options + +This template is more involved than the regular [cookiecutter-pypackage](https://github.com/audreyr/cookiecutter-pypackage). + +First generate your project: + +```bash +cookiecutter gh:FragileTech/ml-ops-quickstart +``` + +You will be asked for these fields: + +> **Note**: Fields that work together usually use the same prefix. If you answer "no" on the first one, then the rest won't have any effect so just ignore them. Maybe in the future Cookiecutter will allow option hiding or something like a wizard. + +### `full_name` + +**Default**: + +```python +"Guillem Duran Ballester" +``` + +Main author of this library or application (used in `AUTHORS.md` and `pyproject.toml`). + +Can be set in your `~/.cookiecutterrc` config file. + +### `email` + +**Default**: + +```python +"guillem@fragile.tech" +``` + +Contact email of the author (used in `AUTHORS.md` and `pyproject.toml`). + +Can be set in your `~/.cookiecutterrc` config file. + +### `website` + +**Default**: + +```python +"fragile.tech" +``` + +Website of the author (used in `AUTHORS.md`). + +Can be set in your `~/.cookiecutterrc` config file. + +### `repo_username` + +**Default**: + +```python +"FragileTech" +``` + +Repository username of this project (used for repository link). + +Can be set in your `~/.cookiecutterrc` config file. + +### `project_name` + +**Default**: + +```python +"ML Ops Quickstart" +``` + +Verbose project name, used in headings (docs, README, etc). + +### `repo_hosting_domain` + +**Default**: + +```python +"github.com" +``` + +Use `"no"` for no hosting (various links will disappear). You can also use `"gitlab.com"` and such, but various things will be broken. + +### `repo_name` + +**Default**: + +```python +"ml-ops-quickstart" +``` + +Repository name on hosting service (and project's root directory name). + +### `package_name` + +**Default**: + +```python +"mloq" +``` + +Python package name (whatever you would import). + +### `distribution_name` + +**Default**: + +```python +"mloq" +``` + +PyPI distribution name (what you would `pip install`). + +### `module_name` + +**Default**: + +```python +"core" +``` + +This template assumes there's going to be an "implementation" module inside your package. + +### `project_short_description` + +**Default**: + +```python +"Automate project creation following ML best practices." +``` + +One-line description of the project (used in `README.md` and `pyproject.toml`). + +### `release_date` + +**Default**: + +```python +"today" +``` + +Release date of the project (ISO 8601 format), defaults to today (used in `CHANGELOG.md`). + +### `year_from` + +**Default**: + +```python +"2024" +``` + +Copyright start year. + +### `year_to` + +**Default**: + +```python +"2024" +``` + +Copyright end year. + +### `version` + +**Default**: + +```python +"0.1.0" +``` + +Release version (see `.bumpversion.cfg` and in Sphinx `conf.py`). + +### `command_line_interface` + +**Default**: + +```python +"click" +``` + +Option to enable a CLI (a bin/executable file). Available options: + +- `plain` - a very simple command. +- `argparse` - a command implemented with `argparse`. +- `click` - a command implemented with [click](http://click.pocoo.org/) - which you can use to build more complex commands. +- `no` - no CLI at all. + +### `command_line_interface_bin_name` + +**Default**: + +```python +"mloq" +``` + +Name of the CLI bin/executable file (set the console script name in `pyproject.toml`). + +### `license` + +**Default**: + +```python +"MIT license" +``` + +License to use. Available options: + +- MIT license +- BSD 2-Clause License +- BSD 3-Clause License +- ISC license +- Apache Software License 2.0 +- GNU Lesser General Public License v3 or later (LGPLv3+) +- GNU Lesser General Public License v3 (LGPLv3) +- GNU Lesser General Public License v2.1 or later (LGPLv2+) +- GNU Lesser General Public License v2.1 (LGPLv2) +- no + +What license to pick? https://choosealicense.com/ + +### `codecov` + +**Default**: + +```python +"yes" +``` + +Enable pushing coverage data to Codecov and add badge in `README.md`. + +### `sphinx_docs` + +**Default**: + +```python +"yes" +``` + +Have Sphinx documentation. + +### `sphinx_docs_hosting` + +**Default**: + +```python +"https://ml-ops-quickstart.readthedocs.io/" +``` + +Leave as default if your documentation will be hosted on ReadTheDocs. If your documentation will be hosted elsewhere (such as GitHub Pages or GitLab Pages), enter the top-level URL. + +### `pypi_badge` + +**Default**: + +```python +"yes" +``` + +By default, this will insert links to your project's page on PyPI.org. If you choose `"no"`, then these links will not be created. + +### `pypi_disable_upload` + +**Default**: + +```python +"no" +``` + +If you specifically want to be sure your package will never be accidentally uploaded to PyPI, you can pick `"yes"`. + +## Developing the project + +To format and lint the code: + +```bash +rye run style +``` + +To run all the tests, just run: + +```bash +rye run test +``` + +To see all the Hatch environments: + +```bash +rye run hatch env show +``` + +To only build the docs: + +```bash +rye run build-docs +``` + +To build and verify that the built package is proper and perform other code QA checks: + +```bash +rye run check +``` + +## Releasing the project + +Before releasing your package on PyPI, you should have all the tests in the different environments passing. + +### Version management + +This template provides a basic bumpversion configuration. It's as simple as running: + +- `bumpversion patch` to increase version from `1.0.0` to `1.0.1`. +- `bumpversion minor` to increase version from `1.0.0` to `1.1.0`. +- `bumpversion major` to increase version from `1.0.0` to `2.0.0`. + +You should read [Semantic Versioning 2.0.0](http://semver.org/) before bumping versions. + +### Building and uploading + +TODO + +## Changelog + +See [CHANGELOG.md](https://github.com/FragileTech/ml-ops-quickstart/blob/main/CHANGELOG.md). + +## Questions & answers + +**There's no Makefile?** + +Sorry, no `Makefile` yet. The Hatch environments stand for whatever you'd have in a `Makefile`. + +**Why is the version stored in several files (`pkg/__init__.py`, `pyproject.toml`, `docs/conf.py`)?** + +We cannot use a metadata/version file[^1] because this template is to be used with both distributions of packages (dirs with `__init__.py`) and modules (simple `.py` files that go straight into `site-packages`). There's no good place for that extra file if you're distributing modules. + +But this isn't so bad—bumpversion manages the version string quite neatly. + +[^1]: Example, an `__about__.py` file. + +## Not Exactly What You Want? + +No way, this is the best. 😜 + +If you have criticism or suggestions, please open up an Issue or Pull Request. diff --git a/pyproject.toml b/pyproject.toml index b64132df..fb1c07d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,8 @@ dependencies = [ "flogging", "pre-commit", "click", + "cookiecutter", + "jinja2_time", ] # ToDo: Modify according to your needs! [project.urls] # important URLs for this project @@ -56,7 +58,7 @@ Tracker = "https://github.com/FragileTech/ml-ops-quickstart/issues" mloq = "mloq.__main__:run" [project.optional-dependencies] -lint= ["mypy", "ruff"] +lint= ["mypy", "ruff", "mypy", "isort", "colorama"] test = ["pytest", "pytest-cov", "pytest-xdist"] docs = [ "autoapi", @@ -98,6 +100,14 @@ exclude = [ ] [tool.rye.scripts] +build = {chain=[ + "cookiecutter templates/mlops -f --replay", + "hatch run template:build" +]} +template = {chain=[ + "cookiecutter templates/mlops -f --replay", + "hatch run template:all" +]} lint = { cmd = "hatch run lint:all" } style = { cmd = "hatch run lint:style" } check = { cmd = "hatch run lint:check" } @@ -151,6 +161,7 @@ exclude = [ "config", "mloq-template", "templates", + "docs/conf.py", ] # Same as Black. line-length = 99 @@ -162,7 +173,7 @@ select = [ "FLY", "FIX", "FURB", "N", "NPY", "INP", "ISC", "PERF", "PIE", "PL", "PTH", "RET", "RUF", "S", "T10", - "TD", "T20", "UP", "YTT", "W", + "TD", "T20", "UP", "YTT", "W", "I", ] ignore = [ "D100", "D211", "D213", "D104", "D203", "D301", "D407", "S101", @@ -195,7 +206,7 @@ docstring-quotes = "double" [tool.ruff.lint.per-file-ignores] "__init__.py" = ["E402", "F401"] "cli.py" = ["PLC0415", "D205", "D400", "D415"] -"**/docs/**" = ["INP001", "PTH100"] +"**/docs/**" = ["INP001", "PTH100", "E501"] "**/{tests,docs}/*" = [ "E402", "F401", "F811", "D", "S101", "PLR2004", "S105", "PLW1514", "PTH123", "PTH107", "N811", "PLC0415", "ARG002", @@ -211,7 +222,7 @@ quote-style = "double" [tool.isort] profile = "black" -line_length = "99" +line_length = 99 multi_line_output = 3 order_by_type = false force_alphabetical_sort_within_sections = true @@ -221,13 +232,18 @@ include_trailing_comma = true color_output = true lines_after_imports = 2 honor_noqa = true -skip = [".venv", "venv"] -skip_glob = ["*.pyx"] +skip = [".venv", "venv", "docs/conf.py", "mloq-template/docs/conf.py", "templates", "mloq-template"] +skip_glob = ["*.pyx", "*/docs/conf.py"] [tool.ruff.lint.isort] known-first-party = ["mloq"] forced-separate = ["conftest"] force-single-line = true +order-by-type = false +force-sort-within-sections = true +combine-as-imports = true +lines-after-imports = 2 +detect-same-package = true [tool.ruff.lint.flake8-pytest-style] fixture-parentheses = false @@ -238,6 +254,7 @@ ban-relative-imports = "all" [tool.mypy] files = ["src/mloq", "tests"] +exclude = ["docs/conf.py", "mloq-template/docs/conf.py", "templates"] disallow_untyped_defs = false follow_imports = "normal" # "silent" for not following ignore_missing_imports = true @@ -247,7 +264,7 @@ warn_no_return = false warn_unused_ignores = true [[tool.mypy.overrides]] -module = "*.templates.*" +module = "*templates*" ignore_errors = true [tool.pylint.master] @@ -340,6 +357,7 @@ build = [ "cp CHANGELOG.md docs/source/project/", "cp CONTRIBUTING.md docs/source/project/", "cp README.md docs/source/project/", + "jupyter-book config sphinx docs/", "jupyter-book build docs/ ", "uv pip freeze > docs/requirements.txt", ] @@ -347,22 +365,29 @@ validate = "linkchecker --config .linkcheckerrc --ignore-url=/reference --ignore build-check = ["build"] #, "validate"] serve = "python3 -m http.server --directory docs/_build/html {args}" sphinx = [ - "jupyter-book config sphinx docs/ --overwrite", + "jupyter-book config sphinx docs/", "sphinx-build -b html docs/ docs/_build/html", "uv pip freeze > docs/requirements.txt", ] docs = ["build", "serve"] +# Environment to build the template repository +[tool.hatch.envs.template] +description = "Build and and test the project's mloq-template" +features = ["docs", "lint", "test"] +[tool.hatch.envs.template.scripts] +docs = ["cd mloq-template", "rye run build-docs"] +lint = ["cd mloq-template", "rye run lint"] +test = ["cd mloq-template", "rye run test"] +build = ["lint", "docs", "test"] +all = ["build", "python3 -m http.server --directory mloq-template/docs/_build/html {args}"] + # Lint environment [tool.hatch.envs.lint] description = "Run linting checks with ruff and mypi" template = "lint" # don't inherit from default! features = ["lint"] skip-install = true -dependencies = [ - "mypy", - "ruff", -] [tool.hatch.envs.lint.scripts] typing = [ "echo \"MYPY VERSION: `mypy --version`\"", @@ -372,14 +397,8 @@ style = [ "echo \"RUFF VERSION: `ruff --version`\"", "ruff check --fix-only --unsafe-fixes {args:.}", "ruff format {args:.}", ] -check = [ - "ruff check {args:.}", "ruff format --diff {args:.}","mypy {args:}" , -] -all = [ - "style", - "check", - "typing", -] +check = ["ruff check {args:.}", "ruff format --diff {args:.}"] +all = ["style", "check", "typing"] # Test matrix for various Python versions replacing the functionality of tox [[tool.hatch.envs.py-test.matrix]] diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 00000000..7a5b3129 --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,475 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: true +# with-sources: false +# generate-hashes: false +# universal: true + +-e file:. +accessible-pygments==0.0.5 + # via pydata-sphinx-theme +alabaster==0.7.16 + # via sphinx +anyio==4.6.0 + # via httpx +appnope==0.1.4 ; platform_system == 'Darwin' + # via ipykernel +arrow==1.3.0 + # via cookiecutter + # via jinja2-time +astroid==3.3.5 + # via sphinx-autoapi + # via sphinx-autodoc2 +asttokens==2.4.1 + # via stack-data +attrs==24.2.0 + # via jsonschema + # via jupyter-cache + # via referencing +autoapi==2.0.1 + # via ml-ops-quickstart +babel==2.16.0 + # via pydata-sphinx-theme + # via sphinx +backports-tarfile==1.2.0 ; python_full_version < '3.12' + # via jaraco-context +beautifulsoup4==4.12.3 + # via pydata-sphinx-theme +binaryornot==0.4.4 + # via cookiecutter +certifi==2024.8.30 + # via httpcore + # via httpx + # via requests +cffi==1.17.1 ; (platform_python_implementation != 'PyPy' and sys_platform == 'linux') or platform_python_implementation == 'PyPy' or implementation_name == 'pypy' + # via cryptography + # via pyzmq + # via zstandard +cfgv==3.4.0 + # via pre-commit +chardet==5.2.0 + # via binaryornot +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via cookiecutter + # via hatch + # via jupyter-book + # via jupyter-cache + # via ml-ops-quickstart + # via sphinx-external-toc + # via userpath +colorama==0.4.6 + # via click + # via ipython + # via ml-ops-quickstart + # via pytest + # via sphinx +comm==0.2.2 + # via ipykernel +cookiecutter==2.6.0 + # via ml-ops-quickstart +coverage==7.6.1 + # via pytest-cov +cryptography==43.0.1 ; sys_platform == 'linux' + # via secretstorage +debugpy==1.8.6 + # via ipykernel +decorator==5.1.1 + # via ipython +distlib==0.3.8 + # via virtualenv +distro==1.9.0 + # via ruyaml +docutils==0.20.1 + # via myst-parser + # via pybtex-docutils + # via pydata-sphinx-theme + # via sphinx + # via sphinx-rtd-theme + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +exceptiongroup==1.2.2 ; python_full_version < '3.11' + # via anyio + # via ipython + # via pytest +execnet==2.1.1 + # via pytest-xdist +executing==2.1.0 + # via stack-data +fastjsonschema==2.20.0 + # via nbformat +filelock==3.16.1 + # via virtualenv +flogging==0.0.23 + # via ml-ops-quickstart +greenlet==3.1.1 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64') + # via sqlalchemy +h11==0.14.0 + # via httpcore +hatch==1.12.0 +hatchling==1.25.0 + # via hatch +httpcore==1.0.6 + # via httpx +httpx==0.27.2 + # via hatch +hyperlink==21.0.0 + # via hatch +identify==2.6.1 + # via pre-commit +idna==3.10 + # via anyio + # via httpx + # via hyperlink + # via requests +imagesize==1.4.1 + # via sphinx +importlib-metadata==8.5.0 + # via jupyter-cache + # via keyring + # via myst-nb +iniconfig==2.0.0 + # via pytest +ipykernel==6.29.5 + # via myst-nb +ipython==8.28.0 + # via ipykernel + # via myst-nb +isort==5.13.2 + # via ml-ops-quickstart +jaraco-classes==3.4.0 + # via keyring +jaraco-context==6.0.1 + # via keyring +jaraco-functools==4.1.0 + # via keyring +jedi==0.19.1 + # via ipython +jeepney==0.8.0 ; sys_platform == 'linux' + # via keyring + # via secretstorage +jinja2==3.1.4 + # via cookiecutter + # via jinja2-time + # via jupyter-book + # via myst-parser + # via sphinx + # via sphinx-autoapi +jinja2-time==0.2.0 + # via ml-ops-quickstart +jsonschema==4.23.0 + # via jupyter-book + # via nbformat +jsonschema-specifications==2023.12.1 + # via jsonschema +jupyter-book==1.0.2 + # via ml-ops-quickstart +jupyter-cache==1.0.0 + # via ml-ops-quickstart + # via myst-nb +jupyter-client==8.6.3 + # via ipykernel + # via nbclient +jupyter-core==5.7.2 + # via ipykernel + # via jupyter-client + # via nbclient + # via nbformat +keyring==25.4.1 + # via hatch +latexcodec==3.0.0 + # via pybtex +linkify-it-py==2.0.3 + # via jupyter-book + # via ml-ops-quickstart +markdown-it-py==3.0.0 + # via mdit-py-plugins + # via myst-parser + # via rich +markupsafe==2.1.5 + # via jinja2 +matplotlib-inline==0.1.7 + # via ipykernel + # via ipython +mdit-py-plugins==0.4.2 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +more-itertools==10.5.0 + # via jaraco-classes + # via jaraco-functools +mypy==1.11.2 + # via ml-ops-quickstart +mypy-extensions==1.0.0 + # via mypy +myst-nb==1.1.2 + # via jupyter-book + # via ml-ops-quickstart +myst-parser==2.0.0 + # via jupyter-book + # via ml-ops-quickstart + # via myst-nb +nbclient==0.10.0 + # via jupyter-cache + # via myst-nb +nbformat==5.10.4 + # via jupyter-cache + # via myst-nb + # via nbclient +nest-asyncio==1.6.0 + # via ipykernel +nodeenv==1.9.1 + # via pre-commit +packaging==24.1 + # via hatch + # via hatchling + # via ipykernel + # via pydata-sphinx-theme + # via pytest + # via sphinx + # via sphinx-jupyterbook-latex +parso==0.8.4 + # via jedi +pathspec==0.12.1 + # via hatchling +pexpect==4.9.0 + # via hatch + # via ipython +platformdirs==4.3.6 + # via hatch + # via jupyter-core + # via virtualenv +pluggy==1.5.0 + # via hatchling + # via pytest +pre-commit==4.0.0 + # via ml-ops-quickstart +prompt-toolkit==3.0.48 + # via ipython +psutil==6.0.0 + # via ipykernel +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data +pybtex==0.24.0 + # via pybtex-docutils + # via sphinxcontrib-bibtex +pybtex-docutils==1.0.3 + # via sphinxcontrib-bibtex +pycparser==2.22 ; (platform_python_implementation != 'PyPy' and sys_platform == 'linux') or platform_python_implementation == 'PyPy' or implementation_name == 'pypy' + # via cffi +pydata-sphinx-theme==0.15.4 + # via ml-ops-quickstart + # via sphinx-book-theme +pygments==2.18.0 + # via accessible-pygments + # via ipython + # via pydata-sphinx-theme + # via rich + # via sphinx +pytest==8.3.3 + # via ml-ops-quickstart + # via pytest-cov + # via pytest-xdist +pytest-cov==5.0.0 + # via ml-ops-quickstart +pytest-xdist==3.6.1 + # via ml-ops-quickstart +python-dateutil==2.9.0.post0 + # via arrow + # via jupyter-client +python-slugify==8.0.4 + # via cookiecutter +pywin32==307 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + # via jupyter-core +pywin32-ctypes==0.2.3 ; sys_platform == 'win32' + # via keyring +pyyaml==6.0.2 + # via cookiecutter + # via jupyter-book + # via jupyter-cache + # via myst-nb + # via myst-parser + # via pre-commit + # via pybtex + # via sphinx-autoapi + # via sphinx-external-toc +pyzmq==26.2.0 + # via ipykernel + # via jupyter-client +referencing==0.35.1 + # via jsonschema + # via jsonschema-specifications +requests==2.32.3 + # via cookiecutter + # via sphinx +rich==13.9.2 + # via cookiecutter + # via hatch +rpds-py==0.20.0 + # via jsonschema + # via referencing +ruff==0.6.9 + # via ml-ops-quickstart +ruyaml==0.91.0 + # via ml-ops-quickstart +secretstorage==3.3.3 ; sys_platform == 'linux' + # via keyring +setuptools==75.1.0 + # via ruyaml + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +shellingham==1.5.4 + # via hatch +six==1.16.0 + # via asttokens + # via pybtex + # via python-dateutil +sniffio==1.3.1 + # via anyio + # via httpx +snowballstemmer==2.2.0 + # via sphinx +soupsieve==2.6 + # via beautifulsoup4 +sphinx==7.4.7 + # via autoapi + # via jupyter-book + # via ml-ops-quickstart + # via myst-nb + # via myst-parser + # via pydata-sphinx-theme + # via sphinx-autoapi + # via sphinx-book-theme + # via sphinx-comments + # via sphinx-copybutton + # via sphinx-design + # via sphinx-external-toc + # via sphinx-jupyterbook-latex + # via sphinx-multitoc-numbering + # via sphinx-rtd-theme + # via sphinx-thebe + # via sphinx-togglebutton + # via sphinxcontrib-bibtex + # via sphinxcontrib-jquery + # via sphinxext-opengraph +sphinx-autoapi==3.3.2 + # via ml-ops-quickstart +sphinx-autodoc2==0.5.0 + # via ml-ops-quickstart +sphinx-book-theme==1.1.3 + # via jupyter-book + # via ml-ops-quickstart +sphinx-comments==0.0.3 + # via jupyter-book +sphinx-copybutton==0.5.2 + # via jupyter-book + # via ml-ops-quickstart +sphinx-design==0.6.1 + # via jupyter-book +sphinx-external-toc==1.0.1 + # via jupyter-book +sphinx-jupyterbook-latex==1.0.0 + # via jupyter-book +sphinx-multitoc-numbering==0.1.3 + # via jupyter-book +sphinx-rtd-theme==2.0.0 + # via ml-ops-quickstart +sphinx-thebe==0.3.1 + # via jupyter-book +sphinx-togglebutton==0.3.2 + # via jupyter-book + # via ml-ops-quickstart +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-bibtex==2.6.3 + # via jupyter-book + # via ml-ops-quickstart +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via sphinx +sphinxcontrib-jquery==4.1 + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-mermaid==0.9.2 + # via ml-ops-quickstart +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via sphinx +sphinxext-opengraph==0.9.1 + # via ml-ops-quickstart +sqlalchemy==2.0.35 + # via jupyter-cache +stack-data==0.6.3 + # via ipython +tabulate==0.9.0 + # via jupyter-cache +text-unidecode==1.3 + # via python-slugify +tomli==2.0.2 ; python_full_version <= '3.11' + # via coverage + # via hatchling + # via mypy + # via pytest + # via sphinx + # via sphinx-autodoc2 +tomli-w==1.0.0 + # via hatch +tomlkit==0.13.2 + # via hatch +tornado==6.4.1 + # via ipykernel + # via jupyter-client +traitlets==5.14.3 + # via comm + # via ipykernel + # via ipython + # via jupyter-client + # via jupyter-core + # via matplotlib-inline + # via nbclient + # via nbformat +trove-classifiers==2024.9.12 + # via hatchling +types-python-dateutil==2.9.0.20241003 + # via arrow +typing-extensions==4.12.2 + # via anyio + # via astroid + # via ipython + # via mypy + # via myst-nb + # via pydata-sphinx-theme + # via rich + # via sphinx-autodoc2 + # via sqlalchemy +uc-micro-py==1.0.3 + # via linkify-it-py +urllib3==2.2.3 + # via requests +userpath==1.9.2 + # via hatch +uv==0.4.18 + # via hatch +virtualenv==20.26.6 + # via hatch + # via pre-commit +wcwidth==0.2.13 + # via prompt-toolkit +wheel==0.44.0 + # via sphinx-togglebutton +xxhash==3.5.0 + # via flogging +zipp==3.20.2 + # via importlib-metadata +zstandard==0.23.0 + # via hatch diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 00000000..875f34b9 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,401 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: true +# with-sources: false +# generate-hashes: false +# universal: true + +-e file:. +accessible-pygments==0.0.5 + # via pydata-sphinx-theme +alabaster==0.7.16 + # via sphinx +appnope==0.1.4 ; platform_system == 'Darwin' + # via ipykernel +arrow==1.3.0 + # via cookiecutter + # via jinja2-time +astroid==3.3.5 + # via sphinx-autoapi + # via sphinx-autodoc2 +asttokens==2.4.1 + # via stack-data +attrs==24.2.0 + # via jsonschema + # via jupyter-cache + # via referencing +autoapi==2.0.1 + # via ml-ops-quickstart +babel==2.16.0 + # via pydata-sphinx-theme + # via sphinx +beautifulsoup4==4.12.3 + # via pydata-sphinx-theme +binaryornot==0.4.4 + # via cookiecutter +certifi==2024.8.30 + # via requests +cffi==1.17.1 ; implementation_name == 'pypy' + # via pyzmq +cfgv==3.4.0 + # via pre-commit +chardet==5.2.0 + # via binaryornot +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via cookiecutter + # via jupyter-book + # via jupyter-cache + # via ml-ops-quickstart + # via sphinx-external-toc +colorama==0.4.6 + # via click + # via ipython + # via ml-ops-quickstart + # via pytest + # via sphinx +comm==0.2.2 + # via ipykernel +cookiecutter==2.6.0 + # via ml-ops-quickstart +coverage==7.6.1 + # via pytest-cov +debugpy==1.8.6 + # via ipykernel +decorator==5.1.1 + # via ipython +distlib==0.3.8 + # via virtualenv +distro==1.9.0 + # via ruyaml +docutils==0.20.1 + # via myst-parser + # via pybtex-docutils + # via pydata-sphinx-theme + # via sphinx + # via sphinx-rtd-theme + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +exceptiongroup==1.2.2 ; python_full_version < '3.11' + # via ipython + # via pytest +execnet==2.1.1 + # via pytest-xdist +executing==2.1.0 + # via stack-data +fastjsonschema==2.20.0 + # via nbformat +filelock==3.16.1 + # via virtualenv +flogging==0.0.23 + # via ml-ops-quickstart +greenlet==3.1.1 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64') + # via sqlalchemy +identify==2.6.1 + # via pre-commit +idna==3.10 + # via requests +imagesize==1.4.1 + # via sphinx +importlib-metadata==8.5.0 + # via jupyter-cache + # via myst-nb +iniconfig==2.0.0 + # via pytest +ipykernel==6.29.5 + # via myst-nb +ipython==8.28.0 + # via ipykernel + # via myst-nb +isort==5.13.2 + # via ml-ops-quickstart +jedi==0.19.1 + # via ipython +jinja2==3.1.4 + # via cookiecutter + # via jinja2-time + # via jupyter-book + # via myst-parser + # via sphinx + # via sphinx-autoapi +jinja2-time==0.2.0 + # via ml-ops-quickstart +jsonschema==4.23.0 + # via jupyter-book + # via nbformat +jsonschema-specifications==2023.12.1 + # via jsonschema +jupyter-book==1.0.2 + # via ml-ops-quickstart +jupyter-cache==1.0.0 + # via ml-ops-quickstart + # via myst-nb +jupyter-client==8.6.3 + # via ipykernel + # via nbclient +jupyter-core==5.7.2 + # via ipykernel + # via jupyter-client + # via nbclient + # via nbformat +latexcodec==3.0.0 + # via pybtex +linkify-it-py==2.0.3 + # via jupyter-book + # via ml-ops-quickstart +markdown-it-py==3.0.0 + # via mdit-py-plugins + # via myst-parser + # via rich +markupsafe==2.1.5 + # via jinja2 +matplotlib-inline==0.1.7 + # via ipykernel + # via ipython +mdit-py-plugins==0.4.2 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +mypy==1.11.2 + # via ml-ops-quickstart +mypy-extensions==1.0.0 + # via mypy +myst-nb==1.1.2 + # via jupyter-book + # via ml-ops-quickstart +myst-parser==2.0.0 + # via jupyter-book + # via ml-ops-quickstart + # via myst-nb +nbclient==0.10.0 + # via jupyter-cache + # via myst-nb +nbformat==5.10.4 + # via jupyter-cache + # via myst-nb + # via nbclient +nest-asyncio==1.6.0 + # via ipykernel +nodeenv==1.9.1 + # via pre-commit +packaging==24.1 + # via ipykernel + # via pydata-sphinx-theme + # via pytest + # via sphinx + # via sphinx-jupyterbook-latex +parso==0.8.4 + # via jedi +pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' + # via ipython +platformdirs==4.3.6 + # via jupyter-core + # via virtualenv +pluggy==1.5.0 + # via pytest +pre-commit==4.0.0 + # via ml-ops-quickstart +prompt-toolkit==3.0.48 + # via ipython +psutil==6.0.0 + # via ipykernel +ptyprocess==0.7.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' + # via pexpect +pure-eval==0.2.3 + # via stack-data +pybtex==0.24.0 + # via pybtex-docutils + # via sphinxcontrib-bibtex +pybtex-docutils==1.0.3 + # via sphinxcontrib-bibtex +pycparser==2.22 ; implementation_name == 'pypy' + # via cffi +pydata-sphinx-theme==0.15.4 + # via ml-ops-quickstart + # via sphinx-book-theme +pygments==2.18.0 + # via accessible-pygments + # via ipython + # via pydata-sphinx-theme + # via rich + # via sphinx +pytest==8.3.3 + # via ml-ops-quickstart + # via pytest-cov + # via pytest-xdist +pytest-cov==5.0.0 + # via ml-ops-quickstart +pytest-xdist==3.6.1 + # via ml-ops-quickstart +python-dateutil==2.9.0.post0 + # via arrow + # via jupyter-client +python-slugify==8.0.4 + # via cookiecutter +pywin32==307 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + # via jupyter-core +pyyaml==6.0.2 + # via cookiecutter + # via jupyter-book + # via jupyter-cache + # via myst-nb + # via myst-parser + # via pre-commit + # via pybtex + # via sphinx-autoapi + # via sphinx-external-toc +pyzmq==26.2.0 + # via ipykernel + # via jupyter-client +referencing==0.35.1 + # via jsonschema + # via jsonschema-specifications +requests==2.32.3 + # via cookiecutter + # via sphinx +rich==13.9.2 + # via cookiecutter +rpds-py==0.20.0 + # via jsonschema + # via referencing +ruff==0.6.9 + # via ml-ops-quickstart +ruyaml==0.91.0 + # via ml-ops-quickstart +setuptools==75.1.0 + # via ruyaml + # via sphinx-togglebutton + # via sphinxcontrib-bibtex +six==1.16.0 + # via asttokens + # via pybtex + # via python-dateutil +snowballstemmer==2.2.0 + # via sphinx +soupsieve==2.6 + # via beautifulsoup4 +sphinx==7.4.7 + # via autoapi + # via jupyter-book + # via ml-ops-quickstart + # via myst-nb + # via myst-parser + # via pydata-sphinx-theme + # via sphinx-autoapi + # via sphinx-book-theme + # via sphinx-comments + # via sphinx-copybutton + # via sphinx-design + # via sphinx-external-toc + # via sphinx-jupyterbook-latex + # via sphinx-multitoc-numbering + # via sphinx-rtd-theme + # via sphinx-thebe + # via sphinx-togglebutton + # via sphinxcontrib-bibtex + # via sphinxcontrib-jquery + # via sphinxext-opengraph +sphinx-autoapi==3.3.2 + # via ml-ops-quickstart +sphinx-autodoc2==0.5.0 + # via ml-ops-quickstart +sphinx-book-theme==1.1.3 + # via jupyter-book + # via ml-ops-quickstart +sphinx-comments==0.0.3 + # via jupyter-book +sphinx-copybutton==0.5.2 + # via jupyter-book + # via ml-ops-quickstart +sphinx-design==0.6.1 + # via jupyter-book +sphinx-external-toc==1.0.1 + # via jupyter-book +sphinx-jupyterbook-latex==1.0.0 + # via jupyter-book +sphinx-multitoc-numbering==0.1.3 + # via jupyter-book +sphinx-rtd-theme==2.0.0 + # via ml-ops-quickstart +sphinx-thebe==0.3.1 + # via jupyter-book +sphinx-togglebutton==0.3.2 + # via jupyter-book + # via ml-ops-quickstart +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-bibtex==2.6.3 + # via jupyter-book + # via ml-ops-quickstart +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via sphinx +sphinxcontrib-jquery==4.1 + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-mermaid==0.9.2 + # via ml-ops-quickstart +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via sphinx +sphinxext-opengraph==0.9.1 + # via ml-ops-quickstart +sqlalchemy==2.0.35 + # via jupyter-cache +stack-data==0.6.3 + # via ipython +tabulate==0.9.0 + # via jupyter-cache +text-unidecode==1.3 + # via python-slugify +tomli==2.0.2 ; python_full_version <= '3.11' + # via coverage + # via mypy + # via pytest + # via sphinx + # via sphinx-autodoc2 +tornado==6.4.1 + # via ipykernel + # via jupyter-client +traitlets==5.14.3 + # via comm + # via ipykernel + # via ipython + # via jupyter-client + # via jupyter-core + # via matplotlib-inline + # via nbclient + # via nbformat +types-python-dateutil==2.9.0.20241003 + # via arrow +typing-extensions==4.12.2 + # via astroid + # via ipython + # via mypy + # via myst-nb + # via pydata-sphinx-theme + # via rich + # via sphinx-autodoc2 + # via sqlalchemy +uc-micro-py==1.0.3 + # via linkify-it-py +urllib3==2.2.3 + # via requests +virtualenv==20.26.6 + # via pre-commit +wcwidth==0.2.13 + # via prompt-toolkit +wheel==0.44.0 + # via sphinx-togglebutton +xxhash==3.5.0 + # via flogging +zipp==3.20.2 + # via importlib-metadata diff --git a/src/mloq/__init__.py b/src/mloq/__init__.py index 693f041b..ee52a23e 100644 --- a/src/mloq/__init__.py +++ b/src/mloq/__init__.py @@ -1,7 +1,8 @@ -__version__ = "0.1.0" +import flogging from .core import compute + __all__ = [ "compute", ] diff --git a/src/mloq/__main__.py b/src/mloq/__main__.py index 416f8755..eff51480 100644 --- a/src/mloq/__main__.py +++ b/src/mloq/__main__.py @@ -8,8 +8,12 @@ """ import sys + +import flogging + from mloq.cli import run if __name__ == "__main__": + flogging.setup() sys.exit(run()) diff --git a/templates/mlops b/templates/mlops index ec8a25de..063686ac 160000 --- a/templates/mlops +++ b/templates/mlops @@ -1 +1 @@ -Subproject commit ec8a25de84e0b3ebfb464b8957206f5c28c2dc9e +Subproject commit 063686acac8ba07964df2eb6ef8cc2e50133732a From 855a1eb3c5d271d5c10dd3b9424f415b690ea617 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Fri, 8 Nov 2024 11:16:43 +0100 Subject: [PATCH 11/12] Update README and workflow names. Add config draft Signed-off-by: guillemdb --- .github/workflows/build.yml | 4 ++-- README.md | 14 ++++++++++++++ config/config.yml | 0 mloq-template | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 config/config.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fef3201..9d4ae719 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,13 +81,13 @@ jobs: rye pin --relaxed cpython@${{ matrix.python-version }} rye sync --all-features - - name: Run Pytest on MacOS + - name: Run Pytest on MacOS with rye if: ${{ matrix.os == 'macos-latest' }} run: | set -x rye run test - - name: Run code coverage on Ubuntu + - name: Run code coverage on Ubuntu with rye if: ${{ matrix.os == 'ubuntu-latest' }} run: | set -x diff --git a/README.md b/README.md index 2de89179..65746df8 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,20 @@ Automate project creation following ML best practices. * License: MIT license +## Setting up the repository + +1. Clone the repository: +2. Initialize the submodules: +```bash +git submodule update --init --recursive +``` + +# Testing the templates +1. Navigate to the project root directory. +2. run cookiecutter with the template directory: +```bash +cookiecutter templates/mlops --no-input --overwrite-if-exists +``` ## Features diff --git a/config/config.yml b/config/config.yml new file mode 100644 index 00000000..e69de29b diff --git a/mloq-template b/mloq-template index f13ae31b..faff5db6 160000 --- a/mloq-template +++ b/mloq-template @@ -1 +1 @@ -Subproject commit f13ae31b88554df2ec8fd182fe41b3df55337524 +Subproject commit faff5db63b7b306158a23047b6e5b9ce977f3194 From f3405a035af4f3c869ed327d1a77d5d2f20042a2 Mon Sep 17 00:00:00 2001 From: guillemdb Date: Fri, 8 Nov 2024 11:26:24 +0100 Subject: [PATCH 12/12] Update workflows to checkout submodules Signed-off-by: guillemdb --- .github/workflows/build.yml | 10 ++++ config/config.yml | 101 ++++++++++++++++++++++++++++++++++++ mloq-template | 2 +- templates/mlops | 2 +- 4 files changed, 113 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9d4ae719..d26afc98 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,6 +30,10 @@ jobs: steps: - name: actions/checkout uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + fetch-depth: 100 - name: Setup Rye id: setup-rye uses: eifinger/setup-rye@v4 @@ -52,6 +56,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Setup Rye id: setup-rye @@ -118,6 +124,7 @@ jobs: - name: actions/checkout uses: actions/checkout@v4 with: + submodules: true persist-credentials: false fetch-depth: 100 - name: Set Git user @@ -181,6 +188,7 @@ jobs: - name: actions/checkout uses: actions/checkout@v4 with: + submodules: true persist-credentials: false fetch-depth: 100 - name: current_version @@ -212,6 +220,8 @@ jobs: steps: - name: actions/checkout uses: actions/checkout@v4 + with: + submodules: true - name: Setup Rye id: setup-rye uses: eifinger/setup-rye@v4 diff --git a/config/config.yml b/config/config.yml index e69de29b..f6429da0 100644 --- a/config/config.yml +++ b/config/config.yml @@ -0,0 +1,101 @@ +full_name: Guillem Duran Ballester +email: guillem@fragile.tech +website: fragile.tech +project_name: Novelai +project_slug: "{{ cookiecutter.project_name.lower().replace(' ', '-') }}" +repo_name: "{{ cookiecutter.project_name|lower|replace(' ','-') }}" +repo_main_branch: main +repo_hosting: + - github.com + - gitlab.com + - other domain not listed +repo_hosting_domain: "{{ cookiecutter.repo_hosting if cookiecutter.repo_hosting != 'other domain not listed' else '' }}" +repo_username: FragileTech +repo_url: "https://{{ cookiecutter.repo_hosting_domain }}/{{ cookiecutter.repo_username }}/{{ cookiecutter.repo_name }}" +package_name: "{{ cookiecutter.project_name|lower|replace(' ','_')|replace('-','_') }}" +distribution_name: "{{ cookiecutter.package_name|replace('_','-') }}" +module_name: core +function_name: compute +project_short_description: Automatic generation of book series with LLMs +release_date: today +year_from: "{% now 'utc', '%Y' %}" +year_to: "{% now 'utc', '%Y' %}" +target_python_version: + - 3.10 + - 3.8 + - 3.9 + - 3.11 + - 3.12 +lock_file_support: false +version: 0.0.0 +license: + - MIT license + - BSD 2-Clause License + - BSD 3-Clause License + - ISC license + - Apache Software License 2.0 + - GNU Lesser General Public License v3 or later (LGPLv3+) + - GNU Lesser General Public License v3 (LGPLv3) + - GNU Lesser General Public License v2.1 or later (LGPLv2+) + - GNU Lesser General Public License v2.1 (LGPLv2) + - no +pypi_badge: + - yes + - no +pypi_disable_upload: + - no + - yes +pre_commit: + - yes + - no +formatter_quote_style: + - double + - single +line_length: 99 +docstring_code_line_length: 99 +command_line_interface: + - click + - plain + - argparse + - no +command_line_interface_bin_name: "{{ cookiecutter.distribution_name }}" +codecov: + - yes + - no +sphinx_docs: + - yes + - no +sphinx_docs_hosting: "https://{{ cookiecutter.repo_name|replace('.', '') }}.readthedocs.io/" +github_actions: + - yes + - no +github_actions_osx: + - yes + - no +github_actions_windows: + - yes + - no +__pypi_badge_options: + yes: true + no: false +__pypi_disable_upload_options: + yes: true + no: false +__codecov_options: + yes: true + no: false +__sphinx_docs_options: + yes: true + no: false +__github_actions_options: + yes: true + no: false +__github_actions_osx_options: + yes: true + no: false +__github_actions_windows_options: + yes: true + no: false +_extensions: + - pylibrary.JsonQuoteExtension + - jinja2_time.TimeExtension diff --git a/mloq-template b/mloq-template index faff5db6..38dcee18 160000 --- a/mloq-template +++ b/mloq-template @@ -1 +1 @@ -Subproject commit faff5db63b7b306158a23047b6e5b9ce977f3194 +Subproject commit 38dcee18624b31b220db28c177b3b51caad5f206 diff --git a/templates/mlops b/templates/mlops index 063686ac..52c1a6d9 160000 --- a/templates/mlops +++ b/templates/mlops @@ -1 +1 @@ -Subproject commit 063686acac8ba07964df2eb6ef8cc2e50133732a +Subproject commit 52c1a6d9c3e07d322e31ad07e4e66bb5ab21fcc5