diff --git a/.flake8 b/.flake8 index 53a4353..10cfc5a 100644 --- a/.flake8 +++ b/.flake8 @@ -46,6 +46,8 @@ rst-roles = class, func, ref, + cite:p, + cite:t, rst-directives = envvar, exception, diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..9dfd4ac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,89 @@ +name: Bug report +description: Report something that is broken or incorrect +labels: bug +body: + - type: markdown + attributes: + value: | + **Note**: Please read [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) + detailing how to provide the necessary information for us to reproduce your bug. In brief: + * Please provide exact steps how to reproduce the bug in a clean Python environment. + * In case it's not clear what's causing this bug, please provide the data or the data generation procecure. + * Sometimes it is not possible to share the data but usually it is possible to replicate problems on publicly + available datasets or to share a subset of your data. + + - type: textarea + id: report + attributes: + label: Report + description: A clear and concise description of what the bug is. + validations: + required: true + + - type: textarea + id: versions + attributes: + label: Version information + description: | + Please paste below the output of + + ```python + import session_info + session_info.show(html=False, dependencies=True) + ``` + placeholder: | + ----- + anndata 0.8.0rc2.dev27+ge524389 + session_info 1.0.0 + ----- + asttokens NA + awkward 1.8.0 + backcall 0.2.0 + cython_runtime NA + dateutil 2.8.2 + debugpy 1.6.0 + decorator 5.1.1 + entrypoints 0.4 + executing 0.8.3 + h5py 3.7.0 + ipykernel 6.15.0 + jedi 0.18.1 + mpl_toolkits NA + natsort 8.1.0 + numpy 1.22.4 + packaging 21.3 + pandas 1.4.2 + parso 0.8.3 + pexpect 4.8.0 + pickleshare 0.7.5 + pkg_resources NA + prompt_toolkit 3.0.29 + psutil 5.9.1 + ptyprocess 0.7.0 + pure_eval 0.2.2 + pydev_ipython NA + pydevconsole NA + pydevd 2.8.0 + pydevd_file_utils NA + pydevd_plugins NA + pydevd_tracing NA + pygments 2.12.0 + pytz 2022.1 + scipy 1.8.1 + setuptools 62.5.0 + setuptools_scm NA + six 1.16.0 + stack_data 0.3.0 + tornado 6.1 + traitlets 5.3.0 + wcwidth 0.2.5 + zmq 23.1.0 + ----- + IPython 8.4.0 + jupyter_client 7.3.4 + jupyter_core 4.10.0 + ----- + Python 3.9.13 | packaged by conda-forge | (main, May 27 2022, 16:58:50) [GCC 10.3.0] + Linux-5.18.6-arch1-1-x86_64-with-glibc2.35 + ----- + Session information updated at 2022-07-07 17:55 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..5cad625 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Scverse Community Forum + url: https://discourse.scverse.org/ + about: If you have questions about “How to do X”, please ask them here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..4830371 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,11 @@ +name: Feature request +description: Propose a new feature for cookiecutter-scverse-instance +labels: enhancement +body: + - type: textarea + id: description + attributes: + label: Description of feature + description: Please describe your suggestion for a new feature. It might help to describe a problem or use case, plus any alternatives that you have considered. + validations: + required: true diff --git a/.github/workflows/sync.yaml b/.github/workflows/sync.yaml new file mode 100644 index 0000000..5219499 --- /dev/null +++ b/.github/workflows/sync.yaml @@ -0,0 +1,46 @@ +name: Sync Template + +on: + workflow_dispatch: + schedule: + - cron: "0 2 * * *" # every night at 2:00 UTC + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install dependencies + # for now, pin cookiecutter version, due to https://github.com/cruft/cruft/issues/166 + run: python -m pip install --upgrade cruft "cookiecutter<2" pre-commit toml + - name: Find Latest Tag + uses: oprypin/find-latest-tag@v1.1.0 + id: get-latest-tag + with: + repository: scverse/cookiecutter-scverse + releases-only: false + sort-tags: true + regex: '^v\d+\.\d+\.\d+$' # vX.X.X + - name: Sync + run: | + cruft update --checkout ${{ steps.get-latest-tag.outputs.tag }} --skip-apply-ask --project-dir . + - name: Create Pull Request + uses: peter-evans/create-pull-request@v4 + with: + commit-message: Automated template update from cookiecutter-scverse + branch: template-update + title: Automated template update from cookiecutter-scverse + body: | + A new version of the [scverse cookiecutter template](https://github.com/scverse/cookiecutter-scverse/releases) + got released. This PR adds all new changes to your repository and helps to to stay in sync with + the latest best-practice template maintained by the scverse team. + + **If a merge conflict arised, a `.rej` file with the rejected patch is generated. You'll need to + manually merge these changes.** + + For more information about the template sync, please refer to the + [template documentation](https://cookiecutter-scverse-instance.readthedocs.io/en/latest/developer_docs.html#automated-template-sync). diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d567446..aed6150 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: hooks: - id: black - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.1 + rev: v3.0.0-alpha.2 hooks: - id: prettier - repo: https://github.com/asottile/blacken-docs @@ -44,7 +44,7 @@ repos: - id: trailing-whitespace - id: check-case-conflict - repo: https://github.com/myint/autoflake - rev: v1.7.1 + rev: v1.7.6 hooks: - id: autoflake args: @@ -64,7 +64,16 @@ repos: - flake8-bugbear - flake8-blind-except - repo: https://github.com/asottile/pyupgrade - rev: v3.0.0 + rev: v3.1.0 hooks: - id: pyupgrade args: [--py3-plus, --py38-plus, --keep-runtime-typing] + - repo: local + hooks: + - id: forbid-to-commit + name: Don't commit rej files + entry: | + Cannot commit .rej files. These indicate merge conflicts that arise during automated template updates. + Fix the merge conflicts manually and remove the .rej files. + language: fail + files: '.*\.rej$' diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 170325e..9e5d5fa 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,6 +6,7 @@ build: python: "3.10" sphinx: configuration: docs/conf.py + # disable this for more lenient docs builds fail_on_warning: true python: install: diff --git a/README.md b/README.md index ea21fd5..22fd6cd 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Please refer to the [documentation][link-docs]. In particular, the ## Installation You need to have Python 3.8 or newer installed on your system. If you don't have -Python installed, we recommend installing [Miniconda](https://docs.conda.io/en/latest/miniconda.html). +Python installed, we recommend installing [Mambaforge](https://github.com/conda-forge/miniforge#mambaforge). There are several alternative options to install cookiecutter-scverse-instance: diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst new file mode 100644 index 0000000..49f45ed --- /dev/null +++ b/docs/_templates/autosummary/class.rst @@ -0,0 +1,65 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. add toctree option to make autodoc generate the pages + +.. autoclass:: {{ objname }} + +{% block attributes %} +{% if attributes %} +Attributes table +~~~~~~~~~~~~~~~~~~ + +.. autosummary:: +{% for item in attributes %} + ~{{ fullname }}.{{ item }} +{%- endfor %} +{% endif %} +{% endblock %} + +{% block methods %} +{% if methods %} +Methods table +~~~~~~~~~~~~~ + +.. autosummary:: +{% for item in methods %} + {%- if item != '__init__' %} + ~{{ fullname }}.{{ item }} + {%- endif -%} +{%- endfor %} +{% endif %} +{% endblock %} + +{% block attributes_documentation %} +{% if attributes %} +Attributes +~~~~~~~~~~~ + +{% for item in attributes %} +{{ item }} +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoattribute:: {{ [objname, item] | join(".") }} +{%- endfor %} + +{% endif %} +{% endblock %} + +{% block methods_documentation %} +{% if methods %} +Methods +~~~~~~~ + +{% for item in methods %} +{%- if item != '__init__' %} +{{ item }} +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automethod:: {{ [objname, item] | join(".") }} +{%- endif -%} +{%- endfor %} + +{% endif %} +{% endblock %} diff --git a/docs/api.md b/docs/api.md index f6d84db..7c1ad45 100644 --- a/docs/api.md +++ b/docs/api.md @@ -34,4 +34,5 @@ :toctree: generated pl.basic_plot + pl.BasicClass ``` diff --git a/docs/conf.py b/docs/conf.py index b2f3dbd..c7c77c5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,10 +17,11 @@ # -- Project information ----------------------------------------------------- info = metadata("cookiecutter-scverse-instance") -project = info["Name"] +project_name = info["Name"] author = info["Author"] copyright = f"{datetime.now():%Y}, {author}." version = info["Version"] +repository_url = "https://github.com/" + "scverse" + "/" + project_name # The full version, including alpha/beta/rc tags release = info["Version"] @@ -33,7 +34,7 @@ html_context = { "display_github": True, # Integrate GitHub "github_user": "scverse", # Username - "github_repo": project, # Repo name + "github_repo": project_name, # Repo name "github_version": "main", # Version "conf_py_path": "/docs/", # Path in the checkout to the docs root } @@ -43,15 +44,14 @@ # Add any Sphinx extension module names here, as strings. # They can be extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - "myst_parser", + "myst_nb", + "sphinx_copybutton", "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.autosummary", "sphinx.ext.napoleon", "sphinxcontrib.bibtex", "sphinx_autodoc_typehints", - "scanpydoc.definition_list_typed_field", - "nbsphinx", "sphinx.ext.mathjax", *[p.stem for p in (HERE / "extensions").glob("*.py")], ] @@ -65,13 +65,31 @@ napoleon_use_rtype = True # having a separate entry generally helps readability napoleon_use_param = True myst_heading_anchors = 3 # create anchors for h1-h3 +myst_enable_extensions = [ + "amsmath", + "colon_fence", + "deflist", + "dollarmath", + "html_image", + "html_admonition", +] +myst_url_schemes = ("http", "https", "mailto") +nb_output_stderr = "remove" +nb_execution_mode = "off" +nb_merge_streams = True +typehints_defaults = "braces" + +source_suffix = { + ".rst": "restructuredtext", + ".ipynb": "myst-nb", + ".myst": "myst-nb", +} intersphinx_mapping = { "anndata": ("https://anndata.readthedocs.io/en/stable/", None), "numpy": ("https://numpy.org/doc/stable/", None), } -nbsphinx_execute = "never" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -84,10 +102,16 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = "furo" +html_theme = "sphinx_book_theme" html_static_path = ["_static"] +html_title = project_name + +html_theme_options = { + "repository_url": repository_url, + "use_repository_button": True, +} -pygments_style = "sphinx" +pygments_style = "default" nitpick_ignore = [ # If building the documentation fails because of a missing link that is outside your control, diff --git a/docs/developer_docs.md b/docs/developer_docs.md index d6f79eb..c95a9a6 100644 --- a/docs/developer_docs.md +++ b/docs/developer_docs.md @@ -20,6 +20,7 @@ On the RTD dashboard choose "Import a Project" and follow the instructions to ad that break the documentation. To do so, got to `Admin -> Advanced Settings`, check the `Build pull requests for this projects` option, and click `Save`. For more information, please refer to the [official RTD documentation](https://docs.readthedocs.io/en/stable/pull-requests.html). +- If you find the RTD builds are failing, you can disable the `fail_on_warning` option in `.readthedocs.yaml`. ### Coverage tests with _Codecov_ @@ -92,6 +93,8 @@ The following pre-commit checks are for errors and inconsistencies: - **check-case-conflict**: check files that would conflict with case-insensitive file systems. - [pyupgrade](https://github.com/asottile/pyupgrade): upgrade syntax for newer versions of the language. +- **forbid-to-commit**: Make sure that `*.rej` files cannot be commited. These files are created by the + [automated template sync](#automated-template-sync) if there's a merge conflict and need to be addressed manually. #### Notes on pre-commit checks @@ -240,6 +243,32 @@ in the root of the repository. Continuous integration will automatically run the [scanpy-test-docs]: https://scanpy.readthedocs.io/en/latest/dev/testing.html#writing-tests +### Automated template sync + +Automated template sync is enabled by default. This means that every night, a GitHub action runs [cruft][] to check +if a new version of the `scverse-cookiecutter` template got released. If there are any new changes, a pull request +proposing these changes is created automatically. This helps keeping the repository up-to-date with the latest +coding standards. + +It may happen that a template sync results in a merge conflict. If this is the case a `*.ref` file with the +diff is created. You need to manually address these changes and remove the `.rej` file when you are done. +The pull request can only be merged after all `*.rej` files have been removed. + +:::{tip} +The following hints may be useful to work with the template sync: + +- GitHub automatically disables scheduled actions if there has been not activity to the repository for 60 days. + You can re-enable or manually trigger the sync by navigating to `Actions` -> `Sync Template` in your GitHub repository. +- If you want to ignore certain files from the template update, you can add them to the `[tool.cruft]` section in the + `pyproject.toml` file in the root of your repository. More details are described in the + [cruft documentation][cruft-update-project]. +- To disable the sync entirely, simply remove the file `.github/workflows/sync.yaml`. + +::: + +[cruft]: https://cruft.github.io/cruft/ +[cruft-update-project]: https://cruft.github.io/cruft/#updating-a-project + ### Making a release #### Updating the version number @@ -289,15 +318,15 @@ Please write documentation for your package. This project uses [sphinx][] with t - the [myst][] extension allows to write documentation in markdown/Markedly Structured Text - [Numpy-style docstrings][numpydoc] (through the [napoloen][numpydoc-napoleon] extension). -- Jupyter notebooks as tutorials through [nbsphinx][] (See [Tutorials with nbsphinx](#tutorials-with-nbsphinx-and-jupyter-notebooks)) +- Jupyter notebooks as tutorials through [myst-nb][] (See [Tutorials with myst-nb](#tutorials-with-myst-nb-and-jupyter-notebooks)) - [Sphinx autodoc typehints][], to automatically reference annotated input and output types See the [scanpy developer docs](https://scanpy.readthedocs.io/en/latest/dev/documentation.html) for more information on how to write documentation. -### Tutorials with nbsphinx and jupyter notebooks +### Tutorials with myst-nb and jupyter notebooks -The documentation is set-up to render jupyter notebooks stored in the `docs/notebooks` directory using [nbsphinx][]. +The documentation is set-up to render jupyter notebooks stored in the `docs/notebooks` directory using [myst-nb][]. Currently, only notebooks in `.ipynb` format are supported that will be included with both their input and output cells. It is your reponsibility to update and re-run the notebook whenever necessary. @@ -305,8 +334,6 @@ If you are interested in automatically running notebooks as part of the continuo out [this feature request](https://github.com/scverse/cookiecutter-scverse/issues/40) in the `cookiecutter-scverse` repository. -[nbsphinx]: https://github.com/spatialaudio/nbsphinx - #### Hints - If you refer to objects from other packages, please add an entry to `intersphinx_mapping` in `docs/conf.py`. Only @@ -329,7 +356,7 @@ open _build/html/index.html [codecov docs]: https://docs.codecov.com/docs [pre-commit.ci]: https://pre-commit.ci/ [readthedocs.org]: https://readthedocs.org/ -[nbshpinx]: https://github.com/spatialaudio/nbsphinx +[myst-nb]: https://myst-nb.readthedocs.io/en/latest/ [jupytext]: https://jupytext.readthedocs.io/en/latest/ [pre-commit]: https://pre-commit.com/ [anndata]: https://github.com/scverse/anndata diff --git a/docs/extensions/.gitkeep b/docs/extensions/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/docs/extensions/typed_returns.py b/docs/extensions/typed_returns.py new file mode 100644 index 0000000..9447813 --- /dev/null +++ b/docs/extensions/typed_returns.py @@ -0,0 +1,29 @@ +# code from https://github.com/theislab/scanpy/blob/master/docs/extensions/typed_returns.py +# with some minor adjustment +import re + +from sphinx.application import Sphinx +from sphinx.ext.napoleon import NumpyDocstring + + +def _process_return(lines): + for line in lines: + m = re.fullmatch(r"(?P\w+)\s+:\s+(?P[\w.]+)", line) + if m: + # Once this is in scanpydoc, we can use the fancy hover stuff + yield f'-{m["param"]} (:class:`~{m["type"]}`)' + else: + yield line + + +def _parse_returns_section(self, section): + lines_raw = list(_process_return(self._dedent(self._consume_to_next_section()))) + lines = self._format_block(":returns: ", lines_raw) + if lines and lines[-1]: + lines.append("") + return lines + + +def setup(app: Sphinx): + """Set app.""" + NumpyDocstring._parse_returns_section = _parse_returns_section diff --git a/pyproject.toml b/pyproject.toml index 72f08cf..f619056 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,11 @@ maintainers = [ urls.Documentation = "https://cookiecutter-scverse-instance.readthedocs.io/" urls.Source = "https://github.com/scverse/cookiecutter-scverse-instance" urls.Home-page = "https://github.com/scverse/cookiecutter-scverse-instance" -dependencies = ["anndata"] +dependencies = [ + "anndata", + # for debug logging (referenced from the issue template) + "session-info" +] [project.optional-dependencies] dev = [ @@ -29,13 +33,13 @@ dev = [ ] doc = [ "sphinx>=4", - "furo", - "myst-parser", + "sphinx-book-theme>=0.3.3", + "myst-nb", "sphinxcontrib-bibtex>=1.0.0", - "scanpydoc[typehints]>=0.7.4", + "sphinx-autodoc-typehints", # For notebooks - "nbsphinx", - "ipykernel" + "ipykernel", + "sphinx-copybutton", ] test = [ "pytest", @@ -84,3 +88,15 @@ exclude = ''' [tool.jupytext] formats = "ipynb,md" + +[tool.cruft] +skip = [ + "tests", + "src/**/__init__.py", + "src/**/basic.py", + "docs/api.md", + "docs/changelog.md", + "docs/references.bib", + "docs/references.md", + "docs/notebooks/example.ipynb" +] diff --git a/src/cookiecutter_scverse_instance/pl/__init__.py b/src/cookiecutter_scverse_instance/pl/__init__.py index a9cd20a..c2315dd 100644 --- a/src/cookiecutter_scverse_instance/pl/__init__.py +++ b/src/cookiecutter_scverse_instance/pl/__init__.py @@ -1 +1 @@ -from .basic import basic_plot +from .basic import BasicClass, basic_plot diff --git a/src/cookiecutter_scverse_instance/pl/basic.py b/src/cookiecutter_scverse_instance/pl/basic.py index 71b3627..431582b 100644 --- a/src/cookiecutter_scverse_instance/pl/basic.py +++ b/src/cookiecutter_scverse_instance/pl/basic.py @@ -2,6 +2,44 @@ def basic_plot(adata: AnnData) -> int: - """Generate a basic plot for an AnnData object.""" + """Generate a basic plot for an AnnData object. + + Parameters + ---------- + adata + The AnnData object to preprocess. + + Returns + ------- + Some integer value. + """ print("Import matplotlib and implement a plotting function here.") return 0 + + +class BasicClass: + """A basic class. + + Parameters + ---------- + adata + The AnnData object to preprocess. + """ + + def __init__(self, adata: AnnData): + print("Implement a class here.") + + def my_method(self, param: int) -> int: + """A basic method. + + Parameters + ---------- + param + A parameter. + + Returns + ------- + Some integer value. + """ + print("Implement a method here.") + return 0 diff --git a/src/cookiecutter_scverse_instance/pp/basic.py b/src/cookiecutter_scverse_instance/pp/basic.py index 936f119..5ff1d41 100644 --- a/src/cookiecutter_scverse_instance/pp/basic.py +++ b/src/cookiecutter_scverse_instance/pp/basic.py @@ -2,6 +2,16 @@ def basic_preproc(adata: AnnData) -> int: - """Run a basic preprocessing on the AnnData object.""" + """Run a basic preprocessing on the AnnData :cite:p:`Wolf2018` object. + + Parameters + ---------- + adata + The AnnData object to preprocess. + + Returns + ------- + Some integer value. + """ print("Implement a preprocessing function here.") return 0 diff --git a/src/cookiecutter_scverse_instance/tl/basic.py b/src/cookiecutter_scverse_instance/tl/basic.py index 4e3387c..d215ade 100644 --- a/src/cookiecutter_scverse_instance/tl/basic.py +++ b/src/cookiecutter_scverse_instance/tl/basic.py @@ -2,6 +2,16 @@ def basic_tool(adata: AnnData) -> int: - """Run a tool on the AnnData object.""" + """Run a tool on the AnnData object. + + Parameters + ---------- + adata + The AnnData object to preprocess. + + Returns + ------- + Some integer value. + """ print("Implement a tool to run on the AnnData object.") return 0