diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..e2c8789ac6e39a487548191e18aa7170ed99693b --- /dev/null +++ b/.flake8 @@ -0,0 +1,33 @@ +[flake8] +doctests = True +exclude = .git, .eggs, __pycache__, docs, dist, venv, .tox, env, envs, .cache +ignore = + # line too long, we use black + E501, + # blank line contains whitespace / final whitespace, we use black + W291, + W293, + # blank line contains whitespace / final whitespace, we use black + E303, + # line break before binary operator + W503, + # wrong flake defaults: see https://github.com/psf/black/issues/315, https://github.com/psf/black/issues/43 + E203, + W503, + W504 +extend-ignore = + RST307, + # Google Python style is not RST until after processed by Napoleon + # See https://github.com/peterjc/flake8-rst-docstrings/issues/17 + RST201,RST203,RST301, +rst-roles = + class, + func, + ref, +rst-directives = + envvar, + exception, +rst-substitutions = + version, +per-file-ignores = + __init__.py:F401,E402 diff --git a/.gitignore b/.gitignore index b99139dcfcfbc2e619b256f64641e402c7f1a14c..4d3531412364513db77ce3b1683e14c64de0fb1e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ __pycache__/ # Distribution / packaging .Python env/ +envs/ build/ develop-eggs/ dist/ @@ -69,6 +70,7 @@ instance/ # Sphinx documentation docs/_build/ +docs/lib/reference/_autosummary/ # PyBuilder target/ @@ -95,6 +97,14 @@ ENV/ # vim files *.sw* +# VisualStudioCode +.vscode/* # Maybe .vscode/**/* instead - see comments +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +**/.history + # folder tmp/ .idea diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a8433061ec7d4ef8dd146584b50b4a25e9998d7d..27339812d00dab517cfb11b823c3ea7b03ce1d66 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,12 @@ -image: python:latest +image: python:3.10.10 -# Change pip's cache directory to be inside the project directory since we can -# only cache local items. variables: + # Change pip's cache directory to be inside the project directory since we can + # only cache local items. PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + # If you do not have access to e.g. databases in the ci, use pytest.mark.skipif to skip a test: + # @pytest.mark.skipif(os.environ.get('TEST_DB') != 'Dynamo') + CI_RUNNING: "true" # Pip's cache doesn't store the python packages # https://pip.pypa.io/en/stable/reference/pip_install/#caching @@ -11,31 +14,32 @@ variables: # If you want to also cache the installed packages, you have to install # them in a virtualenv and cache it as well. cache: + key: one-key-to-rule-them-all paths: - .cache/pip - - venv/ - .cache/apt + - .cache/virtualenvs stages: - build - test - deploy +#commands to run in the Docker container before starting each job before_script: - - pip install virtualenv - - virtualenv venv - - source venv/bin/activate + - pip install poetry + - poetry config cache-dir $(pwd)/.cache + - mkdir -p .cache/apt + - apt-get update -yqq + - apt-get -o dir::cache::archives=".cache/apt" install -y -qq gfortran libopenblas-dev liblapack-dev + - apt-get -o dir::cache::archives=".cache/apt" install -y -qq libgmp-dev + - poetry install --with docs # --extras extra we require + - source $(poetry env info -p)/bin/activate dist: stage: build script: - - mkdir -p .cache/apt - - apt-get update -yqq - - apt-get install -y gfortran libopenblas-dev liblapack-dev - - apt-get -o dir::cache::archives=".cache/apt" install -y -qq gfortran liblapack-dev libgmp-dev - - python setup.py bdist_wheel - # an alternative approach is to install and run: - - pip install dist/* + - poetry build artifacts: paths: - dist/*.whl @@ -46,15 +50,13 @@ dist: pages: stage: build script: - - pip install sphinx sphinx_rtd_theme recommonmark - - cd docs - - make html - - cd .. + - sphinx-build -M html docs docs/_build/ -E -a -j auto --keep-going - mkdir -p public - rm -rf public/* - mv docs/_build/html/* public/ # add it to pages. Pages is exposing public/index.html only: - master + - tags cache: paths: - public @@ -65,44 +67,29 @@ pages: lint: stage: test - before_script: - - pip install -q flake8 script: - flake8 test: stage: test script: - - pip --version - - pip install tox # you can also use tox - - tox - coverage: '/^TOTAL.+?(\d+\%)$/' + - coverage run -m pytest + - coverage report + - coverage xml + - mkdir -p report + - rm -rf report/* + - mv coverage.xml report/ + coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' artifacts: - # paths: - # pa- report/unit reports: - junit: - - report/junit.xml + coverage_report: + coverage_format: cobertura + path: report/coverage.xml pypi: - image: docker.km3net.de/base/python:3 stage: deploy - cache: {} - before_script: - - echo "Starting upload to pypi" script: - # Check if current_version is already uploaded - - VERSION=$((python -c "import configparser; config = configparser.ConfigParser(); config.read('setup.cfg'); print(config['bumpversion']['current_version'])") 2>&1) - - MODULE_NAME=$((python -c "import configparser; config = configparser.ConfigParser(); config.read('setup.cfg'); print(config['metadata']['name'])") 2>&1) - - PACKAGE_JSON_URL="https://pypi.org/pypi/$MODULE_NAME/json" - - apt-get install -qq -y jq - - PYPI_VERSIONS=$(curl -s "$PACKAGE_JSON_URL" | jq -r '.releases | keys | .[]' | sort -V) - - if [[ $PYPI_VERSIONS =~ $VERSION ]]; then echo "Version $VERSION is already uploaded!"; exit 1; fi - # Version not already uploaded so do it now. - - echo "Uploading version $VERSION" - - pip install -U twine - - python setup.py sdist - - twine upload dist/* + - poetry publish -u $PYPI_USERNAME -p $PYPI_PASSWORD rules: - # for debuggin: git commit -am "deb" && git push && bumpversion patch && git tag -l --sort=-v:refname | head -n 1 | git push origin + # for debugging: git commit -am "deb" && git push && bumpversion patch && git tag -l --sort=-v:refname | head -n 1 | git push origin - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b6d2326ebedc61e8324b46226aabeefae75dce69..ea2002bd234d1469569e462f57e8db0433cba5b8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,26 @@ default_language_version: python: python3 +default_install_hook_types: +- pre-commit +- commit-msg repos: - repo: https://github.com/ambv/black - rev: 22.3.0 + rev: 23.1.0 hooks: - id: black - language_version: python3.10 -- repo: https://gitlab.com/pycqa/flake8 + fail_fast: true + - id: black-jupyter +- repo: https://github.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 + fail_fast: true - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.0 hooks: - id: check-json - id: pretty-format-json - args: ['--indent', '4'] + args: ['--indent', '4', '--autofix'] - id: check-merge-conflict - id: check-toml - id: check-yaml @@ -27,3 +32,17 @@ repos: hooks: - id: blacken-docs additional_dependencies: [black==22.3.0] +- repo: https://github.com/compilerla/conventional-pre-commit + rev: v2.1.1 + hooks: + - id: conventional-pre-commit + fail_fast: true + stages: [commit-msg] + args: [release, feat, fix, ci, docs, style, refactor, build, chore, test, perf, revert] +- repo: https://github.com/python-poetry/poetry + rev: 1.4.2 + hooks: + - id: poetry-check + - id: poetry-lock + stages: [commit-msg] + args: ["--no-update"] diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 1db793f37946026137fbe27e56e648887339190c..0000000000000000000000000000000000000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,25 +0,0 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - - -formats: all - -build: - image: latest - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py - -# Optionally set the version of Python and requirements required to build your docs -python: - version: 3.7 - install: - - method: pip - path: . - extra_requirements: - - docs diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1b9944a17a1e271668c17b9beab1cb17983a652a..03943a93bcb1ad219803832dd7699b1503d8b7f3 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,166 +1,303 @@ -.. highlight:: shell - -============ Contributing ============ +.. + [contributing] -Contributions are welcome, and they are greatly appreciated! Every little bit -helps, and credit will always be given. +.. highlight:: shell -You can contribute in many ways: +Thank you for your interest in contributing to the project! +Your contributions are greatly appreciated, and every little bit helps to make the project better for everyone. Types of Contributions ---------------------- +There are many ways you can contribute to the project, including: + +.. toctree:: + :maxdepth: 2 + + bugs_report + bugs_fix + feat + docu + tests + feedback + +.. _bugs_report: + Report Bugs ~~~~~~~~~~~ -Report bugs at https://gitlab.mpcdf.mpg.de/dboe/tfields/issues. +If you encounter a bug in the project, please report it by `creating an issue on the project's issue tracker <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/issues/>`_. +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 + +If you want quick feedback, it is helpful to mention specific developers +(@developer_name) or @all. This will trigger a mail to the corresponding developer(s). + +.. _bugs_fix: + +Fixing Bugs +~~~~~~~~~~~ + +If you would like to help fix a bug in the project, take a look at the issues tagged as "bug" and "help wanted" in the project's issue tracker. +These issues are open to anyone who wants to help fix them. -If you are reporting a bug, please include: +.. _feat: -* Your operating system name and version. -* Any details about your local setup that might be helpful in troubleshooting. -* Detailed steps to reproduce the bug. +Implementing Features +~~~~~~~~~~~~~~~~~~~~~ -If you want quick feedback, it is helpful to mention speicific developers -(@devloper_name) or @all. This will trigger a mail to the corresponding developer(s). +If you would like to help implement a new feature in the project, take a look at the issues tagged as "enhancement" and "help wanted" in the project's issue tracker. +These issues are open to anyone who wants to help implement them. -Fix Bugs -~~~~~~~~ +.. _docu: -Look through the repository issues for bugs. Anything tagged with "bug" and "help -wanted" is open to whoever wants to implement it. +Writing Documentation +~~~~~~~~~~~~~~~~~~~~~ -Implement Features -~~~~~~~~~~~~~~~~~~ +Good documentation is key to a successful project. +If you would like to help improve the project's documentation, you can contribute by: -Look through the remote issues for features. Anything tagged with "enhancement" -and "help wanted" is open to whoever wants to implement it. +- Adding documentation to the project's code +- Writing new documentation pages or articles +- Improving existing documentation pages or articles -Write Documentation -~~~~~~~~~~~~~~~~~~~ +.. _tests: -`tfields` could always use more :ref:`documentation<Documentation>`, whether as part of the -official `tfields` docs, in docstrings, or even on the web in blog posts, -articles, and such. +Writing Tests +~~~~~~~~~~~~~ -Write Unittests or Doctests -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`tfields` profits a lot from better :ref name="Testing":`testing`. We encourage you to add unittests +with the `pytest` module (`unittest` module is OK, too) in the `tests` directory or doctests (as part of docstrings or in the documentation). -`tfields` profits a lot from better :ref:`testing<Testing>`. We encourage you to add unittests -(in the `tests` directory) or doctests (as part of docstrings or in the documentation). +.. _feedback: Submit Feedback ~~~~~~~~~~~~~~~ -The best way to send feedback is to file an `Issue <https://gitlab.mpcdf.mpg.de/dboe/tfields/issues>`_. +The best way to send feedback is to file an `Issue <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/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 contributions - are welcome :) + are welcome -Get Started! ------------- +Setup and Install +----------------- -Ready to contribute? Here's how to set up `tfields` for local development. +Ready to contribute? +Here's how to set up `tfields` for local development. -1. Fork the `tfields` repo. -2. Clone your fork locally:: +1. If you are part of the project team, you are welcome to submit changes through your own development branch in the main repository. + However, if you are not part of the team, we recommend `forking <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/forks/new/>`_ the project and submitting changes through a pull request from your forked repository. + This allows us to review and discuss your changes before merging them into the main project. +2. Clone the repository to your local machine:: - $ git clone git@gitlab.mpcdf.mpg.de:dboe/tfields.git + .. code-block:: shell -3. Set up your fork for local development:: + git clone git@gitlab.mpcdf.mpg.de:dboe/tfields.git - $ cd tfields/ - $ pip install .[dev] + Change adequately if you forked. -4. Step 3. already installed `pre-commit <https://pre-commit.com/>`_. Initialize it by running:: +3. Set up your the local development environment:: - $ pre-commit install + .. code-block:: shell + + cd tfields/ + make env + + `make env` will setup a virtual conda environment for you, install all packages there and install `pre-commit <https://pre-commit.com/>`_. + +The above will only be necessary once. -5. Create a branch for local development:: +You are now ready to go. +Have a look at + + .. code-block:: shell - $ git checkout -b name-of-your-bugfix-or-feature + make help + +to get an overview over the daily operations that are automatized for you. + +Daily Operations +---------------- + +- Work on the virtual environment (created by `make env`) by activating it:: + + .. code-block:: shell + + conda activate ./env + +- Work on a branch different from `master/main` for local development:: + + .. code-block:: shell + + git checkout -b <name-of-your-bugfix-or-feature> Now you can make your changes locally. -6. When you're done making changes, check that your changes pass flake8 and the - tests:: +- When you're done making changes, check that these pass the linters and tests:: + + .. code-block:: shell - $ make test + make test + +- Commit your changes and push your branch to origin:: -7. Commit your changes and push your branch to origin:: + .. code-block:: shell - $ git add . - $ git commit -m "Your detailed description of your changes." - $ git push origin name-of-your-bugfix-or-feature + git add <files to be added to your commit> + git commit -m "Your detailed description of your changes." + git push origin name-of-your-bugfix-or-feature -8. Submit a pull request through the repository website. +- Submit a `merge request <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/merge_requests/>`_ through the repository website Follow the `Guidelines <#guidelines>` below. + +.. _guidelines: -Pull Request Guidelines ------------------------ +Code Review and Merge Request Guidelines +---------------------------------------- -Before you submit a pull request, check that it meets these guidelines: +All code contributions to the project are reviewed by project maintainers before they are merged. +To ensure that your code contribution is accepted, please follow these guidelines: -1. The pull request should include tests. -2. If the pull request adds functionality, the docs should be updated. Put - your new functionality into a function with a docstring, and add the - feature to the list in README.rst. -3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check - https://gitlab.mpcdf.mpg.de/dboe/tfields/-/merge_requests +Before you submit a `merge request <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/merge_requests/>`_, check that you follow these guidelines: + +1. Write code that is well-documented and easy to understand. +2. Write tests that verify the behavior of your code. + The merge request should work for all supported python versions and operation systems. +3. Use the project's coding style guidelines. +4. Submit a pull request that describes your changes and why they are valuable to the project. +5. Respond to feedback from code reviewers in a timely manner. and make sure that the tests pass for all supported Python versions. Testing ------- -To run tests, use:: +To run tests, use + + .. code-block:: shell - $ make test + make test -To run a subset of tests, you have the following options:: +To run a subset of tests, you have the following options: - $ pytest tests/test_package.py +- You can run a subset of tests by specifying a test file or test name - $ pytest tests/test_package.py::Test_tfields::test_version_type + .. code-block:: shell - $ pytest --doctest-modules docs/usage.rst + pytest tests/test_package.py - $ pytest --doctest-modules tfields/core.py -k "MyClass.funciton_with_doctest" + pytest tests/test_package.py::Test_tfields::test_version_type + +- To test doctests, in docstrings or documentation, use the flag `--doctest-modules` (active by default via pyproject.toml) -Use the '--trace' option to directly jump into a pdb debugger on fails. Check out the coverage of your api with:: + .. code-block:: shell - $ make coverage + pytest --doctest-modules docs/usage.rst + + pytest --doctest-modules tfields/core.py -k "MyClass.function_with_doctest" + +- Use the `--trace` option to directly jump into a pdb debugger on fails. + +To run tests with code coverage, use the command + + .. code-block:: shell + + make coverage + +This will generate an HTML report showing the coverage of each file in the project. Documentation ------------- -To compile the documentation (including automatically generated module api docs), run:: +To compile the documentation (including automatically generated module api docs), run - $ make doc + .. code-block:: shell + + make docs + +To build the documentation and run a live-reloading server, use the command + + .. code-block:: shell + + make docs-serve + +This will start a server that will rebuild the documentation whenever a change is made, and display it in your web browser. Use doctests as much as possible in order to have tested examples in your documentation. -Styleguide +Style Guide ----------- + Please follow the `google style guide <https://google.github.io/styleguide/pyguide.html>`_ illustrated by `this example <https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html>`_. +Additionaly we format *args and **kwargs like so: + + .. code-block:: python + + def fun(param1: int, param2: float, *args, kw1: str = None, **kwargs) + """ + Args: + param1 (int): The first parameter. + param2 (float): description + *args (str): description + Second line. List follow now + + 0: first position is used as ... + 1: second position is used as ... + + further are passed to/consumed by/used for ... + + kw1 (Optional[str]): description. Defaults to None. + Second line of description should be indented. + **kwargs: description + + **sub_key1 (Optional[str]): This key is retrieved by kwargs.pop('sub_key1', Default) + **sub_key2 (Optional[str]): This key is retrieved by kwargs.pop('sub_key1', Default) + + further are passed to/consumed by/used for ... + + ... + """ + + Deploying --------- A reminder for the maintainers on how to deploy. Make sure all your changes are committed. -Then run:: +Then run - $ bump2version patch # possible: major / minor / patch - $ git push - $ git push --tags + .. code-block:: shell -or use the convenient alias for the above (patch increases only):: + make publish - $ make publish +.. dropdown:: Failed CI deploy stage + :icon: alert + + In case of failure in the CI deploy stage after :code:`make publish`, use the following rule to delete the tag (also remote) + + .. code-block:: shell + + make untag + +If something fails on the ci side, you can make use of The CI will then deploy to PyPI if tests pass. + +TODO Notes +---------- + +If you add a `TODO` note to the code, please follow these conventions: + +- Use the format `TODO(@responsible-user): note` to assign responsibility for the note. +- Place the `TODO` note in a docstring near the relevant code. +- Use priority labels like `TODO-0`, `TODO-1`, and `TODO-2` to indicate the urgency of the note. + +We appreciate your contributions to the project and look forward to working with you! diff --git a/LICENSE.rst b/LICENSE.rst index 65acff406d68f658e56df3ee039b1d7c96403cf2..2854fc17c17e5a4e5b16e10a40957009b2e9c0c8 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022, Daniel Böckenhoff +Copyright (c) 2023, Daniel Böckenhoff Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 88b74aa9e682a2e35f8d0e1d42b27203d27599a7..af80cf9e78ae1ff893a2b7a01a1de0d1ccff12a2 100644 --- a/Makefile +++ b/Makefile @@ -1,69 +1,114 @@ -SHELL := /bin/bash # Use bash syntax -CURRENT_PATH := $(shell pwd) -MODULE := $(shell basename "$(CURRENT_PATH)") -VERSION := $(shell python -c "import sys; import $(MODULE); sys.stdout.write($(MODULE).__version__)") -SOURCES := $(shell find $(MODULE) -name '*.py') -DOCUMENTATION := $(shell find . -name '*.rst') -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SPHINXSOURCEDIR = ./docs -SPHINXBUILDDIR = docs/_build -GITSTATUS = $(shell git status --porcelain) +include tools/Makefile.envs + +# OPTIONS part ?= patch -test: FORCE +.PHONY: info +## Show useful debugging information regarding git status, log, and remote config +info: + @printf $(_INFO) "Package" "$(MODULE) v$(VERSION)" ; + @echo "" + @printf $(_INFO) "Git remote" "" ; \ + git remote -v + @echo "" + @printf $(_INFO) "Git log" "" ; \ + git log -8 --graph --oneline --decorate --all + @echo "" + @printf $(_INFO) "Git status" "" ; \ + git status + +.PHONY: env-info +## Show useful debugging information regarding the environment +env-info: + $(CONDA_EXE) config --get + $(CONDA_EXE) info + $(CONDA_EXE) list >> $(DEBUG_FILE) + +## Lint with flake8 only +lint-flake: flake8 tfields tests + +## Lint with pylint only +lint-pylint: pylint tfields tests + +## Lint your code +lint: lint-flake lint-pylint + +## Run your unit tests +test: py.test -coverage: - # coverage run $(MODULE) test +## Build the coverage +coverage-build: py.test --cov=$(MODULE) || true - # coverage report coverage html + +## Build the coverage html and display it +coverage: coverage-build python -m webbrowser htmlcov/index.html -clean: +## Remove pre-commit artifacts +clean-pre-commit: + pre-commit clean + +## Clean the documentation artifacts +clean-docs: + rm -rf docs/_build + +.PHONY: clean +## Clean your project from unnecessary artifacts +clean: clean-docs + rm -f .make.* coverage erase rm -rf htmlcov - rm -rf docs/_build - rm -rf docs/source + rm -rf docs/lib/reference/_autosummary/ rm -rf dist rm -rf build rm -rf report rm -rf .tox rm -rf .pytest_cache rm -rf *.egg-info + find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete # pre-commit clean -publish: - # call optional with argument: make part=minor publish - bump2version $(part) # possible: major / minor / patch - git push - git push --tags +## Export the environment to a requirements.txt file +requirements: pyproject.toml + poetry export --without-hashes --format=requirements.txt > requirements.txt + +## Install docs requirements using poetry +install-docs: +ifeq ($(ENVACTIVE),true) +ifeq ($(DOCS_INSTALLED),false) + ${CONDA_RUN}poetry install --with docs +endif +else + @printf $(_WARN) "docs" "CONDA environment is not active. Make sure to activate the environment ($$ make env-activate) or install the requirements manually ($$ poetry install --with docs). The successful build of the following command is your responsibility." +endif -untag: - # remove last tag. mostly, because publishing failed - git tag -d v$(VERSION) - git push origin :refs/tags/v$(VERSION) - -requirements: setup.cfg - # We have all the information in the setup.cfg file. For some reasons (e.g. bug in setuptools or limitations to use setup.cfg in readthedocs) we still need a requirements file - python -c "import configparser; import os; config = configparser.ConfigParser(); config.read('setup.cfg'); deps = config['options']['install_requires'].split('\n'); deps = [x for x in deps if x]; head = '# Autogenerated by Makefile from setup.cfg install_requies section. Remove this line if you want to fix this file.'; path = 'requirements.txt'; f = open(path, 'r') if os.path.exists(path) else None; line = f.readline() if f else ''; quit('User defined requirements already existing.') if f and not line.startswith(head[:20]) else None; f = open('requirements.txt', 'w'); f.write('\n'.join([head] + deps))" - -doc: Makefile $(SOURCES) $(DOCUMENTATION) docs/conf.py docs/apidoc-template/* - # link apidoc to source and build html documentation with sphinx - python setup.py build_sphinx - # manual version would be - # # link apidoc to source - # sphinx-apidoc -o docs/source/ $(MODULE) - # # build html documentation with sphinx - # # @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(0) - # sphinx-build -M html docs docs/_build - # +## Serve the docs (constant update) +docs-serve: install-docs + ${CONDA_RUN}sphinx-autobuild docs docs/_build/ -j auto --watch tfields + +## Build the documentation +docs-build: install-docs + # "-M html" specifies that we want to build an HTML version of the documentation. + # "docs" specifies the source directory where the documentation files are located. + # "docs/_build/" specifies the output directory where the HTML files will be generated. + # "-a" tells Sphinx to rebuild all files, even if they haven't been modified. + # "-j auto" instructs Sphinx to use multiple processors to speed up the build process. + # "-W" turns on warnings during the build process. + # "--keep-going" tells Sphinx to continue building even if it encounters errors, rather than stopping the build process immediately. + # "-E" flag to show all warnings. Not yet found an option to remove the *args, **kwargs warnings. Should be handled by napoleon when looking to the code. but no Idea + ${CONDA_RUN}sphinx-build -M html docs docs/_build/ -E -a -j auto --keep-going + # No need to call apidoc since autosummary is doing that for you even better. + +## Build and open the documentation +docs: docs-build # open the html slides - python -m webbrowser docs/_build/html/index.html + ${CONDA_RUN}python -m webbrowser docs/_build/html/index.html +## get up to date with the cookiecutter template 'dough'. First check that no changes are existing. update: # get up to date with the cookiecutter template 'dough' # first check that no changes are existing @@ -78,4 +123,47 @@ update: # Starting upgrade cookiecutter_project_upgrader +## build the project for publishing +build: + poetry build + +## check the correctness of the build +check-build: build + twine check dist/* + +## Bump the version and publish the package via ci to pypi. Args: part - one of major / minor / patch(default) -> 'make part=minor publish' +publish: lint-flake test check-build requires_version + # Makefile laggs afterwards with "poetry version --short" properly so we process the output + $(eval NEW_VERSION := $(shell poetry version $(part) --dry-run -s --no-plugins)) + @echo $(GITSTATUS) + @if [ -z $(GITSTATUS) ]; then \ + echo "Working directory clean."; \ + else \ + git status; \ + printf $(_WARN) "WAIT" "Your Git status is not clean!" ; \ + if ! $(MAKE) -s confirm ; then \ + printf $(_ERROR) "KO" "EXIT" ; \ + exit 1; \ + fi \ + fi + poetry version $(part) + git commit -am "release: v$(NEW_VERSION)" + git tag "v$(NEW_VERSION)" + git push + git push --tags + +## remove last tag. mostly, because publishing failed +untag: + # remove last tag. mostly, because publishing failed + printf $(_WARN) "WAIT" "Do you want to delete the current version tag and push the change?" ; \ + @if [ $(MAKE) -s confirm ]; then \ + git tag -d v$(VERSION) ; \ + git push origin :refs/tags/v$(VERSION) ; \ + else \ + printf $(_ERROR) "ko" "exit" ; \ + exit 1; \ + fi + FORCE: ; + +include tools/Makefile.help diff --git a/README.rst b/README.rst index cdccdae486d546387a34a5388623f78c8fb2ce55..0b38c3c43b39d65f6b3397d6e0503df4282fe6f9 100644 --- a/README.rst +++ b/README.rst @@ -1,52 +1,69 @@ -========================= -Introduction to `tfields` -========================= +library documentation +===================== +.. + [shields-start] .. pypi .. image:: https://img.shields.io/pypi/v/tfields.svg :target: https://pypi.python.org/pypi/tfields - .. ci - .. image:: https://img.shields.io/travis/dboe/tfields.svg - :target: https://travis-ci.com/dboe/tfields .. image:: https://gitlab.mpcdf.mpg.de/dboe/tfields/badges/master/pipeline.svg - :target: https://gitlab.mpcdf.mpg.de/dboe/tfields/commits/master + :target: https://gitlab.mpcdf.mpg.de/dboe/tfields/-/pipelines/latest + +.. latest release +.. image:: https://gitlab.mpcdf.mpg.de/dboe/tfields/-/badges/release.svg + :target: https://gitlab.mpcdf.mpg.de/dboe/tfields/-/releases .. coverage .. image:: https://gitlab.mpcdf.mpg.de/dboe/tfields/badges/master/coverage.svg :target: https://gitlab.mpcdf.mpg.de/dboe/tfields/commits/master -.. readthedocs -.. image:: https://readthedocs.org/projects/tfields/badge/?version=latest - :target: https://tfields.readthedocs.io/en/latest/?badge=latest - :alt: Documentation Status - -.. pyup crosschecks your dependencies. Github is default, gitlab more complicated: https://pyup.readthedocs.io/en/latest/readme.html#run-your-first-update - .. image:: https://pyup.io/repos/github/dboe/tfields/shield.svg - :target: https://pyup.io/repos/github/dboe/tfields/ - :alt: Updates +.. docs +.. image:: https://img.shields.io/website-up-down-green-red/https/dboe.pages.mpcdf.de/tfields.svg?label=docs + :target: https://dboe.pages.mpcdf.de/tfields +.. pre-commit .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white :target: https://github.com/pre-commit/pre-commit :alt: pre-commit +.. + [shields-end] + +Overview +-------- + +.. + [overview-start] Tensors, tensor fields, graphs, mesh manipulation, CAD and more on the basis of numpy.ndarrays. All objects keep track of their coordinate system. Symbolic math operations work for object manipulation. +.. + [overview-end] Licensed under the ``MIT License`` Resources ---------- +~~~~~~~~~ + +.. + [resources-start] * Source code: https://gitlab.mpcdf.mpg.de/dboe/tfields -* Documentation: https://tfields.readthedocs.io +* Documentation: https://dboe.pages.mpcdf.de/tfields * Pypi: https://pypi.python.org/pypi/tfields +.. + [resources-end] + + Features --------- +~~~~~~~~ + +.. + [features-start] The following features should be highlighted: @@ -55,6 +72,9 @@ The following features should be highlighted: * TensorMaps with fields * Mesh manipulation by graph theory * TODO + +.. + [features-end] Review of alternative/supplementary APIs ---------------------------------------- diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..bf91f342d2ca94fc00d952513d6f08b723f3e55c --- /dev/null +++ b/conftest.py @@ -0,0 +1,52 @@ +import pytest +import unittest.mock +import logging + + +# To add auto-imports for doctests +# import numpy +# @pytest.fixture(autouse=True) +# def add_np(doctest_namespace): +# doctest_namespace["np"] = numpy + + +# Patching plotting +@pytest.fixture(scope="session", autouse=True) +def default_session_fixture(request): + """ + :type request: _pytest.python.SubRequest + :return: + """ + log = logging.getLogger() + log.info("Patching `show`") + patch = unittest.mock.patch("matplotlib.pyplot.show") + if not request.config.getoption("--plot"): + patch.start() + + +def pytest_addoption(parser): + parser.addoption( + "--plot", action="store_true", default=False, help="Activate plot tests" + ) + parser.addoption( + "--slow", action="store_true", default=False, help="Activate slow tests" + ) + + +def pytest_configure(config): + config.addinivalue_line( + "markers", "plot: mark test as requiring visual assertion by human" + ) + config.addinivalue_line("markers", "slow: mark test as slow") + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--plot") or config.getoption("--slow"): + return + skip_plot = pytest.mark.skip(reason="need --plot option to run") + skip_slow = pytest.mark.skip(reason="need --slow option to run") + for item in items: + if "plot" in item.keywords: + item.add_marker(skip_plot) + if "slow" in item.keywords: + item.add_marker(skip_slow) diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 8607fce7795fab193404dac8f276a887a9c90a15..0000000000000000000000000000000000000000 --- a/docs/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -# Minimal makefile for Sphinx documentation -# -MODULE_PATH := $(shell cd ..; pwd) -MODULE := $(shell basename "$(MODULE_PATH)") - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -apidocs: $(MODULEPATH)/$(MODULE)/*.py apidoc-templates/* - sphinx-apidoc -o source/ ../$(MODULE) - -.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) diff --git a/docs/_static/index.css b/docs/_static/index.css new file mode 100644 index 0000000000000000000000000000000000000000..40f044c1fe244332b88af989894dd41a0c5a96b8 --- /dev/null +++ b/docs/_static/index.css @@ -0,0 +1,73 @@ +@font-face { + font-family: 'inter'; + src: url('inter.ttf') format('truetype'); +} + +#landing-header { + width: 100%; + display: flex; + flex-wrap: wrap; + margin-left: auto; + margin-top: 1rem; + font-family: Inter; +} + +#landing-header > img { + width: 75%; + margin: auto; +} + +#landing-tagline { + width: 100%; + text-align: center; + font-size: xx-large; +} + + +#landing-overview { + width: 90%; + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-top: 4rem; + margin-left: auto; + margin-right: auto; + justify-content: space-around; +} + + + +@media (min-width: 992px) { + #landing-overview > section { + width: 45% !important; + } +} + +@media (min-width: 1400px) { + #landing-overview > section { + width: 30% !important; + } +} + +#landing-overview > section { + font-size: medium; + background-color: var(--pst-color-on-background); + padding: .75rem; + border-radius: 5px; + text-align: justify; +} + + + +#landing-overview > section h2 { + margin-top: 0; + font-weight: bold; + font-size: medium; + text-transform: uppercase; + color: #edb641; +} + + +#landing-overview > section > p { + width: 100%; +} \ No newline at end of file diff --git a/docs/_static/inter.ttf b/docs/_static/inter.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ec3164efa8fe938310f74b6156d3ad33ccb2fef6 Binary files /dev/null and b/docs/_static/inter.ttf differ diff --git a/docs/_static/style.css b/docs/_static/style.css new file mode 100644 index 0000000000000000000000000000000000000000..ca785ff3c5af458022c6a48edd3d319e3dfe51fb --- /dev/null +++ b/docs/_static/style.css @@ -0,0 +1,93 @@ +.article-container p { + text-align: justify; +} + +#version-warning { + top: 0; + position: sticky; + z-index: 60; + width: 100%; + height: 2.5rem; + display: flex; + column-gap: .5rem; + justify-content: center; + justify-items: center; + align-items: center; + background-color: #eee; + border-bottom: 2px solid #ae2828; +} + +@media (prefers-color-scheme: dark) { + body:not([data-theme="light"]) #version-warning { + background-color: black; + } +} + + +table.docutils { + clear: left; + float: left; + margin: 0 1rem 1rem; +} + + +/* theme customization */ + +html[data-theme="dark"] { + --color-background-primary: #1d2433; + --color-background-secondary: #0c101a; + --pst-color-on-background: #2e374a; + --pst-color-border: #2f2f2f; +} + +html[data-theme="light"] { + --color-background-primary: #f8f9fb; + --color-background-secondary: #fff; +} + +html[data-theme="light"], html[data-theme="dark"] { + --pst-color-background: var(--color-background-primary); + --pst-color-primary: #ffae57; +} + + +@media (min-width: 960px) { + .bd-page-width { + max-width: 100rem; + } +} + +.bd-sidebar-primary { + display: block; + flex: 0 0 18%; +} + + +.bd-content, .bd-sidebar-secondary { + background-color: var(--color-background-secondary); +} + + +.sd-tab-content { + box-shadow: 0 -0.0625rem var(--sd-color-tabs-overline); +} + +div.literal-block-wrapper, pre { + border: none; +} + +.code-block-caption { + background-color: var(--pst-color-surface); +} + +.navbar-persistent--mobile { + margin-left: unset; +} + +.navbar-persistent--mobile:last-of-type { + padding-left: 1rem; +} + +#navbar-icon-links a { + padding-top: .2rem; +} \ No newline at end of file diff --git a/docs/_static/versioning.js b/docs/_static/versioning.js new file mode 100644 index 0000000000000000000000000000000000000000..15f6c9b301978bd4e8583edc2d7696903bd0340c --- /dev/null +++ b/docs/_static/versioning.js @@ -0,0 +1,114 @@ +const loadVersions = async () => { + const res = await fetch(DOCUMENTATION_OPTIONS.URL_ROOT + "versions.json") + if (res.status !== 200) { + return null + } + return await res.json() +} + + +const getCurrentVersion = (versions) => { + const baseURL = new URL(DOCUMENTATION_OPTIONS.URL_ROOT, window.location).href + const parts = window.location.href.replace(baseURL, "").split("/") + if (!parts.length) { + return null + } + const maybeVersion = parts[0] + if (maybeVersion === "lib") { + return versions.latest + } + if (versions.versions.includes(maybeVersion)) { + return maybeVersion + } + return null +} + + +const addVersionWarning = (currentVersion, latestVersion) => { + if (currentVersion === latestVersion) { + return + } + + const navbarMain = document.getElementById("navbar-main") + if (!navbarMain) { + return + } + + const container = document.createElement("div") + container.id = "version-warning" + + const warningText = document.createElement("span") + warningText.textContent = `You are viewing the documentation for ${currentVersion === "dev" ? "a preview" : "an outdated"} version of this project.` + container.appendChild(warningText) + + const latestLink = document.createElement("a") + latestLink.textContent = "Click here to go to the latest version" + latestLink.href = DOCUMENTATION_OPTIONS.URL_ROOT + "lib" + container.appendChild(latestLink) + + navbarMain.before(container) +} + + +const formatVersionName = (version, isLatest) => version + (isLatest ? " (latest)" : "") + + +const addVersionSelect = (currentVersion, versionSpec) => { + const navEnd = document.getElementById("navbar-end") + if (!navEnd) { + return + } + + const container = document.createElement("div") + container.classList.add("navbar-nav") + + const dropdown = document.createElement("div") + dropdown.classList.add("dropdown") + container.appendChild(dropdown) + + const dropdownToggle = document.createElement("button") + dropdownToggle.classList.add("btn", "dropdown-toggle", "nav-item") + dropdownToggle.setAttribute("data-toggle", "dropdown") + dropdownToggle.setAttribute("type", "button") + dropdownToggle.textContent = `Version: ${formatVersionName(currentVersion, currentVersion === versionSpec.latest)}` + dropdown.appendChild(dropdownToggle) + + const dropdownContent = document.createElement("div") + dropdownContent.classList.add("dropdown-menu") + dropdown.appendChild(dropdownContent) + + for (const version of versionSpec.versions) { + const navItem = document.createElement("li") + navItem.classList.add("nav-item") + + const navLink = document.createElement("a") + navLink.classList.add("nav-link", "nav-internal") + navLink.href = DOCUMENTATION_OPTIONS.URL_ROOT + version + navLink.textContent = formatVersionName(version, version === versionSpec.latest) + navItem.appendChild(navLink) + + dropdownContent.appendChild(navItem) + } + + navEnd.prepend(container) +} + + +const setupVersioning = (versions) => { + if (versions === null) { + return + } + + const currentVersion = getCurrentVersion(versions) + if (currentVersion === null) { + return + } + + addVersionWarning(currentVersion, versions.latest) + addVersionSelect(currentVersion, versions) +} + + +window.addEventListener("DOMContentLoaded", () => { + loadVersions().then(setupVersioning) +}) diff --git a/docs/_templates/custom-class-template.rst b/docs/_templates/custom-class-template.rst new file mode 100644 index 0000000000000000000000000000000000000000..7c95a18710ac67543f7ebabaa0be1ba7d3ff9c4e --- /dev/null +++ b/docs/_templates/custom-class-template.rst @@ -0,0 +1,34 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: + :show-inheritance: + :inherited-members: + :special-members: __call__, __add__, __mul__, __iter__, __item__ + + {% block methods %} + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + :nosignatures: + {% for item in methods %} + {%- if not item.startswith('_') %} + ~{{ name }}.{{ item }} + {%- endif -%} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/docs/_templates/custom-module-template.rst b/docs/_templates/custom-module-template.rst new file mode 100644 index 0000000000000000000000000000000000000000..d066d0e4dc2e34f399f55ea38d92ed4540787317 --- /dev/null +++ b/docs/_templates/custom-module-template.rst @@ -0,0 +1,66 @@ +{{ fullname | escape | underline}} + +.. automodule:: {{ fullname }} + + {% block attributes %} + {% if attributes %} + .. rubric:: Module attributes + + .. autosummary:: + :toctree: + {% for item in attributes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + .. autosummary:: + :toctree: + :nosignatures: + {% for item in functions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block classes %} + {% if classes %} + .. rubric:: {{ _('Classes') }} + + .. autosummary:: + :toctree: + :template: custom-class-template.rst + :nosignatures: + {% for item in classes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block exceptions %} + {% if exceptions %} + .. rubric:: {{ _('Exceptions') }} + + .. autosummary:: + :toctree: + {% for item in exceptions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} diff --git a/docs/_templates/navbar-nav.html b/docs/_templates/navbar-nav.html new file mode 100644 index 0000000000000000000000000000000000000000..72ed846db9401cf32d433408b92160e437407451 --- /dev/null +++ b/docs/_templates/navbar-nav.html @@ -0,0 +1,32 @@ +{% macro nav_link(label, target) -%} + <li class="nav-item"> + <a class="nav-link nav-internal" href="{{ target if target.startswith('http') else pathto(target) }}">{{ label }}</a> + </li> +{%- endmacro %} + + +<nav class="navbar-nav"> + <p class="sidebar-header-items__title" + role="heading" + aria-level="1" + aria-label="{{ _('Site Navigation') }}"> + {{ _("Site Navigation") }} + </p> + <ul id="navbar-main-elements" class="navbar-nav"> + {% for label, item in navbar_items | items %} + {% if item is string %} + {{ nav_link(label, item) }} + {% else %} + <div class="nav-item dropdown"> + <button class="btn dropdown-toggle nav-item" type="button" + data-toggle="dropdown">{{ label }}</button> + <ul class="dropdown-menu"> + {% for child_label, child_item in item | items %} + {{ nav_link(child_label, child_item) }} + {% endfor %} + </ul> + </div> + {% endif %} + {% endfor %} + </ul> +</nav> diff --git a/docs/about/index.rst b/docs/about/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..befeb16595a8379cde5ae67cac41e4f63c24ca18 --- /dev/null +++ b/docs/about/index.rst @@ -0,0 +1,5 @@ +===== +ABOUT +===== + +.. include:: ../../AUTHORS.rst diff --git a/docs/apidoc-template/toc.rst_t b/docs/apidoc-template/toc.rst_t deleted file mode 100644 index 8d0b85202138fc70888abde9b0aec3ce0679c2af..0000000000000000000000000000000000000000 --- a/docs/apidoc-template/toc.rst_t +++ /dev/null @@ -1,10 +0,0 @@ - -================= -API Documentation -================= - -.. toctree:: - :maxdepth: {{ maxdepth }} -{% for docname in docnames %} - {{ docname }} -{%- endfor %} diff --git a/docs/authors.rst b/docs/authors.rst deleted file mode 100644 index e122f914a87b277e565fc9567af1a7545ec9872b..0000000000000000000000000000000000000000 --- a/docs/authors.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../AUTHORS.rst diff --git a/docs/community/index.rst b/docs/community/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..ac7b6bcf35e98139c791cb5c4b94f08849972d65 --- /dev/null +++ b/docs/community/index.rst @@ -0,0 +1 @@ +.. include:: ../../CONTRIBUTING.rst diff --git a/docs/conf.py b/docs/conf.py index 9f12f9389ddbe0fbc20c0671f5dca4b1500194df..f51b461e9f8f1366a2ccc7ff64987ac326cf2561 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,195 +1,108 @@ -#!/usr/bin/env python -""" -This file is execfile()d with the current directory set to its -containing dir. - -Note that not all possible configuration values are present in this -autogenerated file. - -All configuration values have a default; values that are commented out -serve to show the default. - -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 subprocess -import configparser import datetime +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib -config = configparser.ConfigParser() -script_dir = os.path.dirname(__file__) -path = os.path.join(script_dir, "../setup.cfg") -config.read(path) -__version__ = config["bumpversion"]["current_version"] -name = config["metadata"]["name"] -source_dir = config["build_sphinx"]["source-dir"] +__source_dir = "docs" +with open(os.path.join(os.path.dirname(__file__), "../pyproject.toml"), "rb") as f: + __config = tomllib.load(f) -# -- General configuration --------------------------------------------- +project = __config["tool"]["poetry"]["name"] +author = __config["tool"]["poetry"]["authors"][0] +copyright = str(datetime.date.today().year) + ", " + author +release = __config["tool"]["poetry"]["version"] -# If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = "2.2.0" - -# 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.autosectionlabel", # links - "sphinx.ext.napoleon", - "sphinx.ext.todo", - "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.autosectionlabel", "sphinx.ext.autodoc", - "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx_design", + "sphinx_copybutton", + "sphinxcontrib.mermaid", + "sphinx.ext.viewcode", # Add links to highlighted source code + "sphinx.ext.doctest", # Test snippets in the documentation + "sphinx.ext.autosummary", + "sphinx_autodoc_typehints", # Automatically parse type hints ] +autosummary_generate = True # Turn on sphinx.ext.autosummary - -def setup(app): - """ - Automatically run apidoc - see https://github.com/sphinx-doc/sphinx/issues/1861 - """ - # distinguish when calling from project root dir and from docs dir - if os.path.basename(os.path.abspath(".")) == source_dir: - subprocess.call( - [ - "sphinx-apidoc", - "-o", - "source/", - "../" + name, - "--templatedir", - "apidoc-template", - ] - ) - else: - subprocess.call( - [ - "sphinx-apidoc", - "-o", - source_dir + "/source/", - name, - "--templatedir", - source_dir + "/apidoc-template", - ] - ) - - -# Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] +exclude_patterns = ["_build"] -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - -# General information about the project. -project = name -year = datetime.date.today().year -author = config["metadata"]["author"] -copyright = str(year) + ", " + author - -# The version info for the project you're documenting, acts as replacement -# for |version| and |release|, also used in various other places throughout -# the built documents. -# -# The short X.Y version. -version = __version__ -# The full version, including alpha/beta/rc tags. -release = __version__ - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- 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 = "classic" - -# Theme options are theme-specific and customize the look and feel of a -# theme further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# 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'] - - -# -- Options for HTMLHelp output --------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = name + "doc" - - -# -- Options for LaTeX output ------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), } -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto, manual, or own class]). -latex_documents = [ - (master_doc, name + ".tex", name + " Documentation", author, "manual"), -] - - -# -- Options for manual page output ------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [(master_doc, name, name + " Documentation", [author], 1)] - - -# -- Options for Texinfo output ---------------------------------------- +napoleon_google_docstring = True +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = True +napoleon_use_admonition_for_notes = True +napoleon_use_admonition_for_references = False +napoleon_attr_annotations = True + +html_show_sourcelink = ( + False # Remove 'view source code' from top of page (for html, not python) +) +autodoc_inherit_docstrings = True # If no docstring, inherit from base class +set_type_checking_flag = True # Enable 'expensive' imports for sphinx_autodoc_typehints +nbsphinx_allow_errors = True # Continue through Jupyter errors +# autodoc_typehints = "description" # Sphinx-native method. Not as good as sphinx_autodoc_typehints +add_module_names = False # Remove namespaces from class/method signatures +autoclass_content = ( + "both" # Both the class’ and the __init__ method’s docstring are concatenated +) +autodoc_class_signature = "separated" +autodoc_default_options = { + "show-inheritance": True, +} +autodoc_member_order = "bysource" +autodoc_typehints_format = "short" + +always_document_param_types = True +typehints_defaults = "comma" + +auto_pytabs_min_version = (3, 7) +auto_pytabs_max_version = (3, 11) + +autosectionlabel_prefix_document = True + +suppress_warnings = ["autosectionlabel.*"] + +html_theme = "pydata_sphinx_theme" +html_static_path = ["_static"] +html_css_files = ["style.css"] +html_js_files = ["versioning.js"] +html_favicon = "images/favicon.ico" +html_logo = "images/logo.svg" +html_show_sourcelink = False +html_sidebars = {"about/*": []} +html_title = f"{project} Framework" + +html_theme_options = { + "use_edit_page_button": False, + "show_toc_level": 4, + "navbar_align": "left", + "icon_links": [ + { + "name": "Source", + "url": __config["tool"]["poetry"]["repository"], + "icon": f"fa-brands fa-{'github' if 'github' in __config['tool']['poetry']['repository'] else 'gitlab'}", + "type": "fontawesome", + }, + ], + "navbar_end": ["navbar-icon-links"], + "navbar_persistent": ["search-button", "theme-switcher"], +} -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - name, - name + " Documentation", - author, - name, - "One line description of project.", - "Miscellaneous", - ), -] +html_context = { + "navbar_items": { + "Documentation": "lib/index", + "Community": { + "Contribution guide": "community/index", + }, + "About": "about/index", + } +} diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index e582053ea018c369be05aae96cf730744f1dc616..0000000000000000000000000000000000000000 --- a/docs/contributing.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../CONTRIBUTING.rst diff --git a/docs/cookiecutter_input.json b/docs/cookiecutter_input.json index 539ee6e426ba12a983e41954677ec43c156596ef..c28c84939f530808036d925f4be6923267446a21 100644 --- a/docs/cookiecutter_input.json +++ b/docs/cookiecutter_input.json @@ -1,5 +1,4 @@ { - "_output_dir": "/mnt/data/git/tfields/.git/cookiecutter", "_template": "https://gitlab.com/dboe/dough.git", "author": "Daniel B\u00f6ckenhoff", "continuous_integration": "y", diff --git a/docs/images/favicon.ico b/docs/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e0482a1bebb6c86187c50d00045e5ed9e1316b58 Binary files /dev/null and b/docs/images/favicon.ico differ diff --git a/docs/images/logo.svg b/docs/images/logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..8e8ae8a9db12740245deaac106eadb7037d78179 --- /dev/null +++ b/docs/images/logo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M13 21v2.5l-3-2-3 2V21h-.5A3.5 3.5 0 0 1 3 17.5V5a3 3 0 0 1 3-3h14a1 1 0 0 1 1 1v17a1 1 0 0 1-1 1h-7zm-6-2v-2h6v2h6v-3H6.5a1.5 1.5 0 0 0 0 3H7zM7 5v2h2V5H7zm0 3v2h2V8H7zm0 3v2h2v-2H7z"/></svg> \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 28abcdcb0638450427862045129470ecf835065a..26edbe1df3120d15a961fa41d269884b8044ecba 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,19 +1,74 @@ -Welcome to tfields's documentation! -====================================== +.. div:: sd-text-center sd-text-primary sd-fs-1 sd-font-weight-bolder -sd-text-nowra + + tfields + +.. card:: + :text-align: center + :shadow: none + :class-card: sd-border-0 + + .. include:: ../README.rst + :start-after: [shields-start] + :end-before: [shields-end] + +.. grid:: 1 1 2 2 + :gutter: 1 + + .. grid-item:: + + .. grid:: 1 1 1 1 + :gutter: 1 + + .. grid-item-card:: Overview + :link: lib/overview/index + :link-type: doc + + .. include:: ../README.rst + :start-after: [overview-start] + :end-before: [overview-end] + + .. grid-item-card:: Installation + :link: lib/installation/index + :link-type: doc + + .. include:: lib/installation/index.rst + :start-after: [essence-start] + :end-before: [essence-end] + + .. grid-item:: + + .. grid:: 1 1 1 1 + :gutter: 1 + + .. grid-item-card:: Usage + :link: lib/usage/index + :link-type: doc + + .. include:: lib/usage/index.rst + :start-after: [essence-start] + :end-before: [essence-end] + + .. grid-item-card:: API reference + :link: lib/reference/_autosummary/tfields + :link-type: doc + :text-align: center + + :octicon:`codescan;5em;sd-text-info` + + .. grid-item-card:: API development + :link: community/index + :link-type: doc + + + .. code-block:: shell + + make install + make test + make publish .. toctree:: - :maxdepth: 2 - :caption: Contents: - - readme - installation - usage - source/modules - contributing - authors - -Indices and tables -================== -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + :hidden: + + about/index + community/index + lib/index diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 4fbc5858ba725b89e350ae1e88d6b17149f827cd..0000000000000000000000000000000000000000 --- a/docs/installation.rst +++ /dev/null @@ -1,52 +0,0 @@ -.. highlight:: shell - -============ -Installation -============ - - -Stable release --------------- - -To install tfields, run this command in your terminal: - -.. code-block:: console - - $ pip install tfields - -This is the preferred method to install tfields, as it will always install the most recent stable release. - -If you don't have `pip`_ installed, this `Python installation guide`_ can guide -you through the process. - -.. _pip: https://pip.pypa.io -.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ - - - -From sources ------------- - -The sources for tfields can be downloaded from the `remote repo`_. - -You can either clone the public repository: - -.. code-block:: console - - $ git clone git://gitlab.mpcdf.mpg.de/dboe/tfields - -Or download the `tarball`_: - -.. code-block:: console - - $ curl -OJL https://gitlab.mpcdf.mpg.de/dboe/tfields/tarball/master - -Once you have a copy of the source, you can install it with: - -.. code-block:: console - - $ python setup.py install - - -.. _remote repo: https://gitlab.mpcdf.mpg.de/dboe/tfields -.. _tarball: https://gitlab.mpcdf.mpg.de/dboe/tfields/tarball/master diff --git a/docs/lib/index.rst b/docs/lib/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..50a175d20d6b6074f75ef43944023ab5337da38a --- /dev/null +++ b/docs/lib/index.rst @@ -0,0 +1,14 @@ +Library documentation +===================== + +.. include:: overview/index.rst + +.. toctree:: + :caption: Documentation + :hidden: + :maxdepth: 2 + + overview/index + installation/index + usage/index + reference/index \ No newline at end of file diff --git a/docs/lib/installation/index.rst b/docs/lib/installation/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..759a8a76fd458f8b2a70553f33e7263375ca76dd --- /dev/null +++ b/docs/lib/installation/index.rst @@ -0,0 +1,70 @@ +============ +Installation +============ +.. + How to get it and set it up? + [installation] + +To install tfields, you have the following options: + +.. tab-set:: + + .. tab-item:: PyPi :octicon:`package` + + The preferred method to install tfields is to get the most recent stable release from PyPi: + + .. + [essence-start] + + .. code-block:: shell + + pip install tfields + + .. + [essence-end] + + If you don't have `pip`_ installed, this `Python installation guide`_ can guide you through the process. + + .. _pip: https://pip.pypa.io + .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ + + .. dropdown:: Extras + :icon: star + + Install a special extra: + :code:`pip install tfields[extra]` + + All extras: + :code:`pip install tfields[full]` + + .. tab-item:: Source :octicon:`code` + + First you have to retrieve the source code of tfields. + You have the following options: + + .. tab-set:: + + .. tab-item:: Git :octicon:`git-branch` + + To clone the public repository run + + .. code-block:: shell + + git clone git://gitlab.mpcdf.mpg.de/dboe/tfields + + .. tab-item:: Tarball :octicon:`gift` + + Either download the tarball `here <https://gitlab.mpcdf.mpg.de/dboe/tfields/tarball/master>`_ or run + + .. code-block:: shell + + curl -OJL https://gitlab.mpcdf.mpg.de/dboe/tfields/tarball/master + + + Once you have a copy of the source, navigate inside and install it with: + + .. code-block:: shell + + poetry install + + diff --git a/docs/lib/overview/index.rst b/docs/lib/overview/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..bc3124fff4a97d04a5e0bf65120c8cd633a11adf --- /dev/null +++ b/docs/lib/overview/index.rst @@ -0,0 +1,28 @@ +Overview +======== + +.. + What is it? Why should I use it? + [overview-start] + +.. include:: ../../../README.rst + :start-after: [overview-start] + :end-before: [overview-end] + +.. + [overview-end] + +Features +-------- + +.. + What can it do? + [features] + +.. include:: ../../../README.rst + :start-after: [features-start] + :end-before: [features-end] + +.. + [features-end] + diff --git a/docs/lib/reference/index.rst b/docs/lib/reference/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..799c111a3280523ca7624a72a7e6fc918378f2c2 --- /dev/null +++ b/docs/lib/reference/index.rst @@ -0,0 +1,14 @@ +============= +API reference +============= + +.. + How to dive deeper? Give me the api? + [reference] + +.. autosummary:: + :toctree: _autosummary + :template: custom-module-template.rst + :recursive: + + tfields diff --git a/docs/lib/usage/index.rst b/docs/lib/usage/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..b7fd3e9ec356874fe953d60c76f6802e0362c1ac --- /dev/null +++ b/docs/lib/usage/index.rst @@ -0,0 +1,18 @@ +===== +Usage +===== +.. + How to use? Give me a primer. + [usage] + +.. + [essence-start] + +To use tfields in a project use + +.. code-block:: python + + import tfields + +.. + [essence-end] diff --git a/docs/readme.rst b/docs/readme.rst deleted file mode 100644 index 72a33558153fb57def85612b021ec596ef2a51b9..0000000000000000000000000000000000000000 --- a/docs/readme.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../README.rst diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index c0d9b7748c4e7115601cd41a0ef5e6646ed8f836..0000000000000000000000000000000000000000 --- a/docs/usage.rst +++ /dev/null @@ -1,7 +0,0 @@ -===== -Usage -===== - -To use tfields in a project:: - - >>> import tfields diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000000000000000000000000000000000000..a7ea3a24c98a2226fce098bd9e62d6e216a15d6c --- /dev/null +++ b/environment.yml @@ -0,0 +1,18 @@ +name: tfields +channels: + - conda-forge + # We want to have a reproducible setup, so we don't want default channels, + # which may be different for different users. All required channels should + # be listed explicitly here. + - nodefaults +dependencies: + - python=3.10.* # or don't specify the version and use the latest stable Python + + - mamba + - conda-lock + - "poetry>=1.4" + - pre_commit + - "pip>=21.2" # pip must be mentioned explicitly, or conda-lock will fail + - "numpy>=1.20.0" # https://stackoverflow.com/questions/66060487/valueerror-numpy-ndarray-size-changed-may-indicate-binary-incompatibility-exp + - pip: + - tqdm \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..3d501370bf93db0856fd45d16f88ce829a3bff3f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,129 @@ +[tool.poetry] +name = "tfields" +version = "0.4.0" +description = "Tensors, tensor fields, graphs, mesh manipulation, CAD and more on the basis of numpy.ndarrays. All objects keep track of their coordinate system. Symbolic math operations work for object manipulation." +authors = ["Daniel Böckenhoff <dboe@ipp.mpg.de>"] +license = "MIT License" +readme = "README.rst" +repository = "https://gitlab.mpcdf.mpg.de/dboe/tfields" +documentation = "https://dboe.pages.mpcdf.de/tfields" +classifiers = [ + # find the full list of possible classifiers at https://pypi.org/classifiers/ + "Development Status :: 3 - Alpha", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", +] +keywords = ["tensors", " tensor-fields", " graphs", " mesh", " numpy", " math"] + +[tool.poetry.dependencies] +python = "^3.7" +pathlib = { version = "*", python = "<3.10" } +numpy = ">=1.20.0" +sympy = "<=1.6.2" +scipy = "*" +rna = ">=0.6.3" +sortedcontainers = "*" +numpy-stl = { version = "*", optional = true} + +[tool.poetry.group.dev.dependencies] +poetry-bumpversion = "*" +ensureconda = "*" # is part of conda-lock either way +conda-lock = "*" +tomli = { version = ">=1.1.0", python = "<3.11" } +pytest = "*" +pytest-cov = "*" +pytest-shutil = "*" +pytest-virtualenv = "*" +pytest-fixture-config = "*" +pytest-xdist = "*" +coverage = { version = "*", extras = ["toml"]} +pylint = "*" +flake8 = "*" +ipdb = "*" +mypy = "*" +twine = "*" # for publishing +pre-commit = "*" # https://pre-commit.com/ for hook managment +pre-commit-hooks = "*" +sphinx = "*" # for documentation +cookiecutter_project_upgrader = "*" + +[tool.poetry.group.docs] +optional = true + +[tool.poetry.group.docs.dependencies] +black = ">=22.12.0" +httpx = ">=0.23.2" +uvicorn = ">=0.20.0" +sphinx-autobuild = ">=2021.3.14" +"sphinx-autopackagesummary" = ">=1.3" +sphinx-design = ">=0.3.0" +sphinx = ">=5.3.0" +sphinx-toolbox = ">=3.2.0" +sphinx-copybutton = ">=0.5.1" +sphinxcontrib-mermaid = ">=0.7.1" +pydata-sphinx-theme = ">=0.12.0" +sphinx-autodoc-typehints = ">=1.22" + +[tool.poetry.extras] +io = ["numpy-stl"] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.coverage.report] +show_missing = true +exclude_lines = [ + "pragma: no cover", + "if False", +] +[tool.coverage.run] +omit = [ + "tfields/plotting/*.py" +] + +[tool.poetry_bumpversion.file."tfields/__init__.py"] +search = '__version__ = "{current_version}"' +replace = '__version__ = "{new_version}"' + +[tool.poetry_bumpversion.file."docs/cookiecutter_input.json"] +search = '"package_version": "{current_version}"' +replace = '"package_version": "{new_version}"' + +[tool.pytest.ini_options] +addopts = """ +--doctest-modules +--ignore-glob=*/bin/activate +--ignore=performance +""" +junit_family = "xunit2" + +[tool.tox] +legacy_tox_ini = """ + [tox] + isolated_build = true + skip_missing_interpreters = true + recreate = true + min_version = 4.0 + env_list = + py35 + py36 + py37 + py38 + py39 + py310 + type + + [testenv] + deps = pytest + commands = pytest --import-mode importlib + + [testenv:type] + deps = mypy + commands = mypy src +""" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index ff786503bc7690e7ee5d1d4e7b39afbf66fd7aed..0000000000000000000000000000000000000000 --- a/setup.cfg +++ /dev/null @@ -1,164 +0,0 @@ -[bumpversion] -current_version = 0.4.0 -tag = True -commit = True -message = release-v{new_version} - -[bumpversion:file:tfields/__init__.py] -search = __version__ = '{current_version}' -replace = {new_version} - -[bumpversion:file:setup.py] -search = version='{current_version}' -replace = {new_version} - -[bumpversion:file:docs/cookiecutter_input.json] -search = 'package_version': '{current_version}' -replace = {new_version} - -[metadata] -name = tfields -url = https://gitlab.mpcdf.mpg.de/dboe/tfields -author = Daniel Böckenhoff -author_email = dboe@ipp.mpg.de -classifiers = - Development Status :: 3 - Alpha - License :: OSI Approved :: MIT License - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 -license = MIT License -license_files = LICENSE.rst -description = Tensors, tensor fields, graphs, mesh manipulation, CAD and more on the basis of numpy.ndarrays. All objects keep track of their coordinate system. Symbolic math operations work for object manipulation. -long_description = file: README.rst, LICENSE.rst -long_description_content_type = text/x-rst -keywords = tensors, tensor-fields, graphs, mesh, numpy, math -project_urls = - Documentation = https://tfields.readthedocs.io - Source = https://gitlab.mpcdf.mpg.de/dboe/tfields - -[options] -python_requires = >=3.0 -packages = find: -install_requires = - pathlib;python_version<'3.10' - six - numpy>=1.20.0 - sympy<=1.6.2 # diffgeom changes in 1.7 see unit branch for first implementation of compatibility - scipy - rna>=0.6.3 - sortedcontainers -tests_require = - doctest - unittest - pytest - -[options.packages.find] -exclude = - tests* - performance* - -[options.extras_require] -all = - %(dev)s -dev = - %(docs)s - %(test)s - %(io)s - bump2version==1.0.0 # for incrementing the version - twine # for publishing - sphinx # for documentation - pre-commit # https://pre-commit.com/ for hook managment - pre-commit-hooks - cookiecutter_project_upgrader -docs = - sphinx>=2.2.0 # requires templatedir option in sphinx-apidoc - sphinx_rtd_theme>=0.4.3 -test = - flake8 - pylint - pytest - pytest-cov - coverage - pytest-shutil - pytest-virtualenv - pytest-fixture-config - pytest-xdist - - matplotlib -io = - numpy-stl - -[bdist_wheel] -universal = 1 - -[coverage:report] -show_missing = true -exclude_lines = - pragma: no cover - if False - -[coverage:run] -omit = - tfields/plotting/*.py - -[flake8] -max-line-length = 99 -doctests = True -exclude = .git, .eggs, __pycache__, docs, dist, venv, .tox -ignore = E203 W503 W504 # wrong flake defaults: see https://github.com/psf/black/issues/315, https://github.com/psf/black/issues/43 -per-file-ignores = - __init__.py: F401 - -[pylint.] -ignore = setup.py - -[build_sphinx] -builder = html,man -all-files = 1 -build-dir = docs/_build -source-dir = docs - -[tool:pytest] -addopts = - --doctest-modules -junit_family = xunit2 - -[tox:tox] -minversion = 3.7 -skip_missing_interpreters = true -envlist = - py{35,36,37,38,39,310} -recreate = true - -[gh-actions] -python = - 3.5: py35 - 3.6: py36 - 3.7: py37 - 3.8: py38 - 3.9: py39 - 3.10: py310 - -[testenv] -description = run test suite under {basepython} -deps = - {[options]install_requires} - {[options.extras_require]test} -extras = test -whitelist_externals = rm -commands_pre = - rm -rf **/__pycache__ - rm -rf **/*.pyc -commands = - pytest {envsitepackagesdir}/{[metadata]name} --basetemp="{envtmpdir}" {posargs} --cov={envsitepackagesdir}/{[metadata]name} --junitxml=report/junit.xml --ignore={envsitepackagesdir}/{[metadata]name}/plotting - -[testenv:flake8] -commands = flake8 {[metadata]name}/ tests/ -extras = testing -description = run flake8 under {basepython} diff --git a/setup.py b/setup.py deleted file mode 100644 index fed32f4124460da059f40a27821479e4d69c44aa..0000000000000000000000000000000000000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup(version="0.4.0") diff --git a/tfields/__main__.py b/tfields/__main__.py index 69d2eba855fef38a3207f0287a8e15c8ea72d621..f8dacf54c082119329315567ad540fdff9395c46 100644 --- a/tfields/__main__.py +++ b/tfields/__main__.py @@ -1,6 +1,6 @@ -#!/user/bin/env python """ -w7x option starter +Main entry point for the project when run from the command line +i.e. via $ python -m tfields """ import sys import argparse @@ -17,8 +17,8 @@ class SomeAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): print( - "Example action invoked by manage in namespace: %r with values %r" - " and option string %r" % (namespace, values, option_string) + f"Example action invoked by manage in namespace: {namespace} with values {values}" + " and option string {option_string}." ) setattr(namespace, self.dest, values) diff --git a/tfields/bases/bases.py b/tfields/bases/bases.py index 92d0529ca80d87bd57c4dd21b357e1d838ecdc44..c40d1990ed08cd1d3179d4e791713006d8a157bf 100644 --- a/tfields/bases/bases.py +++ b/tfields/bases/bases.py @@ -11,7 +11,6 @@ import tfields import numpy as np import sympy import sympy.diffgeom -from six import string_types import warnings @@ -22,7 +21,7 @@ def get_coord_system(base): Return: sympy.diffgeom.get_coord_system """ - if isinstance(base, string_types) or ( + if isinstance(base, str) or ( isinstance(base, np.ndarray) and base.dtype.kind in {"U", "S"} ): base = getattr(tfields.bases, str(base)) @@ -45,11 +44,6 @@ def get_coord_system_name(base): """ if isinstance(base, sympy.diffgeom.CoordSystem): base = getattr(base, "name") - # if not (isinstance(base, string_types) or base is None): - # baseType = type(base) - # raise ValueError("Coordinate system must be string_type." - # " Retrieved value '{base}' of type {baseType}." - # .format(**locals())) return str(base) diff --git a/tfields/lib/util.py b/tfields/lib/util.py index cf365652f3efe699b24abb4c923a7bb4dc3723c2..c3730a0ab2f77121e64b887784495de85a246132 100644 --- a/tfields/lib/util.py +++ b/tfields/lib/util.py @@ -3,7 +3,6 @@ Various utility functions """ import itertools import typing -from six import string_types import numpy as np @@ -71,7 +70,7 @@ def flatten(seq, container=None, keep_types=None, key: typing.Callable = None): for item in seq: if ( hasattr(item, "__iter__") - and not isinstance(item, string_types) + and not isinstance(item, str) and not any((isinstance(item, t) for t in keep_types)) ): flatten(item, container, keep_types, key=key) diff --git a/tools/Makefile.envs b/tools/Makefile.envs new file mode 100644 index 0000000000000000000000000000000000000000..e789521182db7a29b26f1493ba3271b3b83ff995 --- /dev/null +++ b/tools/Makefile.envs @@ -0,0 +1,33 @@ +# Environment Management Makefile, adapted from https://github.com/hackalog/make_better_defaults +# +include tools/Makefile.include + +# Create the virtual environment in the process updating the lockfile. Args: prefix - name of local environment. defaults to 'env' -> 'make prefix=env_new command' +$(ENVDIR): + python -c "import tools; tools.env.create('$(prefix)')" + +# Update the environment and create it if necessary +# Depends on +# - changes of environment.yml +# - existence of $(ENVDIR) +$(ENVLOCKFILE): environment.yml $(ENVDIR) + python -c "import tools; tools.env.create('$(prefix)')" + +.PHONY: env-delete +## Delete the virtual (conda) environment for this project. Args: prefix - name of local environment. defaults to 'env' -> 'make prefix=env_new command' +env-delete: + @printf $(_WARN) "env" "Really deleting the conda environment '$(prefix)'?" + @if $(MAKE) -s confirm ; then \ + $(CONDA_EXE) remove --prefix $(ENVDIR) --all -y ; \ + fi + +## Update poetry dependencies +env-update-poetry: pyproject.toml + poetry update + +## Create environment if none is existing, update the environment if it is found. Args: prefix - name of local environment. defaults to 'env' -> 'make prefix=env_new command'. +env: $(ENVLOCKFILE) env-update-poetry + +## Use this to dynamically activate the virtual environment via `eval $(make env-activate)`. Args: prefix - name of local environment. defaults to 'env' -> 'make prefix=env_new command'. +env-activate: + @echo "conda activate $(ENVDIR)" diff --git a/tools/Makefile.help b/tools/Makefile.help new file mode 100644 index 0000000000000000000000000000000000000000..3a39f94f836d9a2e4134a8540abe24ae36693167 --- /dev/null +++ b/tools/Makefile.help @@ -0,0 +1,98 @@ +################################################################################# +# Self Documenting Help for Make Targets # +################################################################################# +# +# The MIT License (MIT) +# Copyright (c) 2016 DrivenData, Inc. <https://github.com/drivendata/cookiecutter-data-science> +# Copyright (c) 2018 Kjell Wooding <https://github.com/hackalog/easy.env> +# +# 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. + +.DEFAULT_GOAL := help + +# Inspired by <http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html> +# sed script explained: +# /^##/: +# * save line in hold space +# * purge line +# * Loop: +# * append newline + line to hold space +# * go to next line +# * if line starts with doc comment, strip comment character off and loop +# * remove target prerequisites +# * append hold space (+ newline) to line +# * replace newline plus comments by `---` +# * print line +# Separate expressions are necessary because labels cannot be delimited by +# semicolon; see <http://stackoverflow.com/a/11799865/1968> +.PHONY: how-help + +print-% : ; @echo $* = $($*) + +HELP_VARS := PROJECT_NAME DEBUG_FILE ARCH + +help-prefix: + @echo "To get started:" + @echo " >>> $$(tput bold)make env$$(tput sgr0)" + @echo " >>> $$(tput bold)conda activate $(PROJECT_NAME)$$(tput sgr0)" + @echo "" + @echo "$$(tput bold)Project Variables:$$(tput sgr0)" + @echo "" + +help: help-prefix $(addprefix print-, $(HELP_VARS)) + @echo + @echo "$$(tput bold)Available rules:$$(tput sgr0)" + @sed -n -e "/^## / { \ + h; \ + s/.*//; \ + :doc" \ + -e "H; \ + n; \ + s/^## //; \ + t doc" \ + -e "s/:.*//; \ + G; \ + s/\\n## /---/; \ + s/\\n/ /g; \ + p; \ + }" ${MAKEFILE_LIST} \ + | LC_ALL='C' sort --ignore-case \ + | awk -F '---' \ + -v ncol=$$(tput cols) \ + -v indent=19 \ + -v col_on="$$(tput setaf 6)" \ + -v col_off="$$(tput sgr0)" \ + '{ \ + printf "%s%*s%s ", col_on, -indent, $$1, col_off; \ + n = split($$2, words, " "); \ + line_length = ncol - indent; \ + for (i = 1; i <= n; i++) { \ + line_length -= length(words[i]) + 1; \ + if (line_length <= 0) { \ + line_length = ncol - indent - length(words[i]) - 1; \ + printf "\n%*s ", -indent, " "; \ + } \ + printf "%s ", words[i]; \ + } \ + printf "\n"; \ + }' \ + | more $(shell test $(shell uname) = Darwin && echo '--no-init --raw-control-chars') + @echo diff --git a/tools/Makefile.include b/tools/Makefile.include new file mode 100644 index 0000000000000000000000000000000000000000..21c0edc09e725f70d61f5c6878da4b6c56accf3f --- /dev/null +++ b/tools/Makefile.include @@ -0,0 +1,82 @@ +SHELL := /bin/bash# Use bash syntax + +# OPTIONS +# part of the version string to bump +part ?= patch +# environment name (prefix for conda) +prefix ?= env + +CURRENT_PATH := $(shell pwd -P) +MODULE := $(shell basename "$(CURRENT_PATH)") +VERSION := +ifneq (,$(shell which poetry 2>/dev/null)) +VERSION := $(shell poetry version --short) +endif +SOURCES := $(shell find $(MODULE) -name '*.py') +DOCUMENTATION := $(shell find . -name '*.rst') +GITSTATUS = $(shell git status --porcelain) +DEBUG_FILE := debug.txt +PROJECT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +# PYTHON_INTERPRETER := python3 +# ARCH := $(shell $(PYTHON_INTERPRETER) -c "import platform; print(platform.platform())") +VIRTUALENV := conda +ENVLOCKFILE := conda-linux-64.lock # conda-$(ARCH).lock ??? how to make compatible with conda-lock +ENVDIR = $(CURRENT_PATH)/$(prefix) +# check if the environment exists +ENV_EXISTS := $(wildcard $(ENVDIR)/bin/activate) +ENVACTIVE := false +ifeq ($(CONDA_DEFAULT_ENV),$(ENVDIR)) + ENVACTIVE = true +endif +CONDA_EXE := $(shell which mamba) +ifeq (,$(CONDA_EXE)) + CONDA_EXE := $(shell which conda) +endif +CONDA_RUN := +# set CONDA_RUN if the environment exists and is not active +ifeq ($(ENV_EXISTS),true) +ifeq ($(ENVACTIVE),false) + CONDA_RUN := conda run -p $(ENVDIR) +endif +endif +ifeq ($(shell command -v tee 2> /dev/null),) + REDIRECT_OUTPUT = >> $(prefix).log 2>&1 +else + REDIRECT_OUTPUT = | tee $(prefix).log +endif +DOCS_INSTALLED := false +ifeq ($(ENVACTIVE),true) +# check if sphinx-build and sphinx-autobuild commands are available +ifeq ($(wildcard $(ENVDIR)/bin/sphinx-build),) + DOCS_INSTALLED = true +endif +endif + + +# The CI environment variable can be set to a non-empty string, +# it'll bypass this command that will "return true", as a "yes" answer. +# To use the "confirm" target inside another target, +# use the " if $(MAKE) -s confirm ; " syntax. +confirm: + @if [[ -z "$(CI)" ]]; then \ + REPLY="" ; \ + read -p "⚠Are you sure? [y/n] > " -r ; \ + if [[ ! $$REPLY =~ ^[Yy]$$ ]]; then \ + printf $(_ERROR) "KO" "Stopping" ; \ + exit 1 ; \ + else \ + printf $(_INFO) "OK" "Continuing" ; \ + exit 0; \ + fi \ + fi +.PHONY: confirm +_WARN := "\033[33m[%s]\033[0m %s\n" # Yellow text for "printf" +_INFO := "\033[32m[%s]\033[0m %s\n" # Green text for "printf" +_ERROR := "\033[31m[%s]\033[0m %s\n" # Red text for "printf" + +## install docs requirements using poetry +requires_version: +ifeq ($(VERSION),) + @printf $(_ERROR) "variables" "$(VERSION) not set - poetry not installed" + exit 0; +endif diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1d8baba7c8a7f089bae6a6b86366d45c5ad4d0a2 --- /dev/null +++ b/tools/__init__.py @@ -0,0 +1,2 @@ +from . import process +from . import env diff --git a/tools/env.py b/tools/env.py new file mode 100644 index 0000000000000000000000000000000000000000..8707bb2eb185987b712fa887f65db5b25b389ee1 --- /dev/null +++ b/tools/env.py @@ -0,0 +1,71 @@ +import shutil +from .process import run_shell_command +import tempfile +import os +from ensureconda.resolve import platform_subdir + + +TMPDIR = tempfile.gettempdir() +BOOTSTRAPDIR = os.path.join(TMPDIR, "bootstrap") +PROJECT_DIR = os.path.abspath(os.path.join(__file__, "../..")) +ENVIRONMENT_YAML = "environment.yml" +ENVIRONMENT_YAML_PATH = os.path.join(PROJECT_DIR, ENVIRONMENT_YAML) +POETRY_LOCK = "poetry.lock" +CONDA_LOCK = f"conda-{platform_subdir()}.lock" + + +def install(conda_run=""): + run_shell_command(f"{conda_run}poetry install") + run_shell_command(f"{conda_run}pre-commit install") + + +def update(conda_run=""): + # Update Poetry packages and re-generate poetry.lock + run_shell_command(f"{conda_run}poetry update") + run_shell_command(f"{conda_run}pre-commit install") + + +def create(name="env"): + """ + This create routine was initially influenced by + https://stackoverflow.com/questions/70851048/does-it-make-sense-to-use-conda-poetry + """ + env_dir = os.path.join(PROJECT_DIR, name) + + requirements_here = ["poetry", "mamba", "conda-lock"] + requirements_not_satisfied = any( + [shutil.which(name) is None for name in requirements_here] + ) + + run_bs = "" + if requirements_not_satisfied: + # Use (and if necessary create) a bootstrap env + if not os.path.exists(BOOTSTRAPDIR): + # use python 3.10 for bootstraping + requirements_here.append("python==3.10") + run_shell_command( + f"conda create --yes -p {BOOTSTRAPDIR} -c conda-forge " + + " ".join(requirements_here) + ) + run_bs = f"conda run -p {BOOTSTRAPDIR} " + + # Create conda lock file(s) from environment.yml or update it if environment.yml has changed. + run_shell_command( + f"{run_bs}conda-lock --check-input-hash -k explicit --conda mamba" + ) + + run_env = f"conda run -p {env_dir} " + if os.path.exists(env_dir): + # update + run_shell_command(f"{run_bs}mamba update --file {CONDA_LOCK} -p {env_dir}") + update(conda_run=run_env) + else: + # create + run_shell_command(f"{run_bs}mamba create --file {CONDA_LOCK} -p {env_dir}") + install(conda_run=run_env) + + # Add conda and poetry lock files + run_shell_command("git add *.lock") + print( + ">>> done. Generated lock files. You might want to run >>> git commit -m 'build(conda): lock-files'" + ) diff --git a/tools/process.py b/tools/process.py new file mode 100644 index 0000000000000000000000000000000000000000..567e32a147904b9169f7c10e607ff908d6433296 --- /dev/null +++ b/tools/process.py @@ -0,0 +1,8 @@ +import shlex +import subprocess + + +def run_shell_command(command_line, *args, **kwargs): + command_line_args = shlex.split(command_line) + print(f"Running command '{command_line}'") + subprocess.call(command_line_args, *args, **kwargs)