diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml deleted file mode 100644 index 3219b26..0000000 --- a/.github/workflows/black.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Black - -on: [push, pull_request] - -jobs: - - lint: - name: Check code format - # We want to run on external PRs, but not on our own internal PRs as they'll be run - # by the push to the branch. Without this if check, checks are duplicated since - # internal PRs match both the push and pull_request events. - if: - github.event_name == 'push' || github.event.pull_request.head.repo.full_name != - github.repository - - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: psf/black@stable - with: - options: "--check --verbose --diff" - src: "setup.py landlab_rest tests" diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000..ed0d4d3 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,25 @@ +name: Check + +on: + pull_request: + types: [labeled, unlabeled, opened, reopened, synchronize] + +jobs: + check-changelog-entry: + name: changelog entry + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + # `towncrier check` runs `git diff --name-only origin/main...`, which + # needs a non-shallow clone. + fetch-depth: 0 + + - name: Check changelog + if: "!contains(github.event.pull_request.labels.*.name, 'Skip Changelog')" + run: | + if ! pipx run towncrier check --compare-with origin/${{ github.base_ref }}; then + echo "Please see https://landlab.readthedocs.io/en/master/development/contribution/index.html?highlight=towncrier#news-entries for guidance." + false + fi diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..bbb4f66 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +name: Documentation + +on: [push, pull_request] + +jobs: + build: + name: Build documentation + # We want to run on external PRs, but not on our own internal PRs as they'll be run + # by the push to the branch. Without this if check, checks are duplicated since + # internal PRs match both the push and pull_request events. + if: + github.event_name == 'push' || github.event.pull_request.head.repo.full_name != + github.repository + + runs-on: ubuntu-latest + + defaults: + run: + shell: bash -l {0} + + steps: + - uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: 3.8 + channels: conda-forge + channel-priority: true + + - name: Show conda installation info + run: | + conda info + + - name: Install dependencies + run: pip install nox + + - name: Build documentation + run: nox -s build-docs diff --git a/.github/workflows/flake8.yml b/.github/workflows/lint.yml similarity index 89% rename from .github/workflows/flake8.yml rename to .github/workflows/lint.yml index bd2d97c..d9bd825 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Flake8 +name: Lint on: [push, pull_request] @@ -23,5 +23,5 @@ jobs: - name: Lint run: | - pip install flake8 - flake8 landlab_rest tests + pip install nox + nox -s lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 56d2fcd..ecbb258 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,27 +33,16 @@ jobs: channels: conda-forge channel-priority: true + - name: Show conda installation info run: | conda info - conda list - name: Install dependencies - run: | - conda install mamba -c conda-forge - mamba install --file=requirements.txt - - - name: Build and install package - run: | - pip install -e . - - - name: Install testing requirements - run: pip install -e .[dev] + run: pip install nox - - name: Test - run: | - python -c 'import landlab_rest; print(landlab_rest.__version__)' - pytest --cov=landlab_rest --cov-report=xml:$(pwd)/coverage.xml -vvv + - name: Build documentation + run: nox -s test - name: Coveralls if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f2808b9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,48 @@ +repos: + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black + language_version: python3.8 + + - repo: https://github.com/asottile/blacken-docs + rev: v1.12.1 + hooks: + - id: blacken-docs + additional_dependencies: [black==21.8b0] + + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + files: \.py$ + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-builtin-literals + - id: check-added-large-files + - id: check-case-conflict + - id: check-toml + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: forbid-new-submodules + - id: trailing-whitespace + + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + + - repo: https://github.com/PyCQA/pydocstyle + rev: 6.1.1 + hooks: + - id: pydocstyle + files: landlab_rest/.*\.py$ + + - repo: https://github.com/asottile/pyupgrade + rev: v3.1.0 + hooks: + - id: pyupgrade + args: [--py38-plus] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..af42761 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,26 @@ +version: 2 + +sphinx: + builder: html + configuration: docs/source/conf.py + fail_on_warning: false + +formats: + - htmlzip + +python: + # version: "3.8" + install: + - requirements: requirements-docs.txt + - requirements: requirements.txt + - method: pip + path: . + system_packages: false + +build: + os: ubuntu-22.04 + tools: + python: "3.8" + jobs: + pre_build: + - sphinx-apidoc -e -force --no-toc -o docs/source/api landlab_rest diff --git a/AUTHORS.rst b/AUTHORS.rst index c010acb..1dc3893 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,15 +1,15 @@ +======= Credits ======= Development Lead ---------------- -* Eric Hutton (@mcflugen) +* `Eric Hutton `_ Contributors ------------ -* Jenny Knuth -* Mark Piper -* Greg Tucker - +* `Jenny Knuth `_ +* `Mark Piper `_ +* `Greg Tucker `_ diff --git a/CHANGES.rst b/CHANGES.rst index 5ebfa5f..4857c11 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,8 @@ -Changelog for landlab-rest -========================== +============= +Release Notes +============= + +.. towncrier-draft-entries:: Not yet released .. towncrier release notes start @@ -45,5 +48,3 @@ Other Changes and Additions - Set up continuous integration on Travis.org. (`#5 `_) - Added unit tests. (`#5 `_) - Added smoke tests for the command line interface, ``start-sketchbook``. (`#7 `_) - - diff --git a/LICENSE.rst b/LICENSE.rst index 5c46e0d..edb8110 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -1,7 +1,7 @@ The MIT License (MIT) ===================== -Copyright © `2022` `Landlab` +Copyright (c) `2022` `Eric Hutton` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -22,4 +22,4 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.rst b/README.rst index 1d8912d..48e6fb0 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,6 @@ +.. image:: https://readthedocs.org/projects/landlab-rest/badge/?version=latest + :target: https://landlab-rest.readthedocs.org + .. image:: https://github.com/landlab/landlab-rest/actions/workflows/test.yml/badge.svg :target: https://github.com/landlab/landlab-rest/actions/workflows/test.yml @@ -7,26 +10,65 @@ .. image:: https://github.com/landlab/landlab-rest/actions/workflows/black.yml/badge.svg :target: https://github.com/landlab/landlab-rest/actions/workflows/black.yml +.. image:: https://github.com/landlab/landlab-rest/actions/workflows/docs.yml/badge.svg + :target: https://github.com/landlab/landlab-rest/actions/workflows/docs.yml + landlab REST ============ -A RESTful interface to landlab graphs. +.. start-intro + +A web service for creating *Landlab* graph structures. Send +requests to retrieve data (as json objects) that describe *Landlab* +model grids. This includes, + +* *x* and *y* coordinates of a grid's nodes and corners +* connectivity the the various grid elements + +Currently available grid types include :class:`~landlab.grid.raster.RasterModelGrid`, +:class:`~landlab.grid.hex.HexModelGrid` and :class:`~landlab.grid.radial.RadialModelGrid`. + +.. end-intro + +For more information, visit `landlab-rest's documentation `_. Quickstart ---------- -Use `conda` to install the necessary requirements and `landlab_rest`, +.. start-quickstart -.. code:: +To get started you will need to install the *landlab-rest* package, which is currently distributed +on `PyPI`_. + +1. Install *landlab-rest* into your current environment. + + .. code:: bash - $ conda install --file=requirements.txt -c conda-forge - $ pip install . + pip install landlab-rest + +2. Start the server. + + .. code:: bash + + start-sketchbook + +3. You can now send queries to the *landlab-rest* service. + + .. code:: bash + + curl https://0.0.0.0:8080/graphs/ + +.. _PyPI: https://pypi.org/project/landlab-rest/ + +.. end-quickstart + +.. start-running Start the server, .. code:: - $ start-sketchbook + start-sketchbook Look at the line containing `Serving on` to see what host and port the server is running on. Alternatively, you can use the `--host` and `--port` @@ -37,19 +79,19 @@ to get a `RasterModelGrid`, .. code:: - $ curl https://0.0.0.0:8080/graphs/raster + curl https://0.0.0.0:8080/graphs/raster For a list of supported graphs .. code:: - $ curl https://0.0.0.0:8080/graphs/ + curl https://0.0.0.0:8080/graphs/ You can pass parameters like, .. code:: - $ curl 'https://0.0.0.0:8080/graphs/raster?shape=4,5&spacing=2.,1.' + curl 'https://0.0.0.0:8080/graphs/raster?shape=4,5&spacing=2.,1.' Docker @@ -72,4 +114,6 @@ Once running, you can then send requests to the server. For example, .. code:: - $ curl https://0.0.0.0/graphs/raster + curl https://0.0.0.0/graphs/raster + +.. end-running diff --git a/docs/Makefile b/docs/Makefile index 69fe55e..ba501f6 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -16,4 +16,4 @@ help: # 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) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/source/_static/favicon.ico b/docs/source/_static/favicon.ico new file mode 100644 index 0000000..8386674 Binary files /dev/null and b/docs/source/_static/favicon.ico differ diff --git a/docs/source/_static/landlab_logo.png b/docs/source/_static/landlab_logo.png new file mode 100644 index 0000000..38a6593 Binary files /dev/null and b/docs/source/_static/landlab_logo.png differ diff --git a/docs/source/_templates/links.html b/docs/source/_templates/links.html index 7e44127..8bc6ad3 100644 --- a/docs/source/_templates/links.html +++ b/docs/source/_templates/links.html @@ -4,4 +4,3 @@

Useful Links

  • landlab_rest @ GitHub
  • Issue Tracker
  • - diff --git a/docs/source/api/.gitignore b/docs/source/api/.gitignore new file mode 100644 index 0000000..b134ab7 --- /dev/null +++ b/docs/source/api/.gitignore @@ -0,0 +1,2 @@ +# auto-generated with sphinx-apidoc +landlab_*.rst diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst new file mode 100644 index 0000000..bd7234e --- /dev/null +++ b/docs/source/api/index.rst @@ -0,0 +1,7 @@ +landlab_rest +============ + +.. toctree:: + :maxdepth: 4 + + landlab_rest diff --git a/docs/source/authors.rst b/docs/source/authors.rst new file mode 100644 index 0000000..7739272 --- /dev/null +++ b/docs/source/authors.rst @@ -0,0 +1 @@ +.. include:: ../../AUTHORS.rst diff --git a/docs/source/changes.rst b/docs/source/changes.rst new file mode 100644 index 0000000..d76c92b --- /dev/null +++ b/docs/source/changes.rst @@ -0,0 +1 @@ +.. include:: ../../CHANGES.rst diff --git a/docs/source/conf.py b/docs/source/conf.py index 93e4119..121dfa3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,17 +10,30 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os # import sys # sys.path.insert(0, os.path.abspath('.')) +import os +import pathlib +from datetime import date + +import landlab_rest # -- Project information ----------------------------------------------------- -project = 'landlab_rest' -copyright = '2019, Eric Hutton' -author = 'Eric Hutton' +docs_dir = os.path.dirname(__file__) +project = "landlab_rest" +author = "Eric Hutton" +copyright = str(date.today().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 = landlab_rest.__version__ +# The full version, including alpha/beta/rc tags. +release = landlab_rest.__version__ # -- General configuration --------------------------------------------------- @@ -28,143 +41,171 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', - 'sphinx.ext.napoleon', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", + "sphinx.ext.napoleon", + "sphinx.ext.autosummary", + "sphinx_inline_tabs", + "sphinxcontrib.towncrier", + "sphinx_copybutton", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" +pygments_dark_style = "monokai" # -- 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 = 'alabaster' +html_theme = "furo" +html_title = "landlab-rest" +html_logo = "_static/landlab_logo.png" # 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 = {} +# html_theme_options = {} +html_theme_options = { + "announcement": None, + "source_repository": "https://github.com/landlab/landlab-rest/", + "source_branch": "master", + "source_directory": "docs/source", + "sidebar_hide_name": True, + "footer_icons": [ + { + "name": "power", + "url": "https://csdms.colorado.edu", + "html": """ + + Powered by CSDMS + """, + "class": "", + }, + ], +} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = "_static/powered-by-logo-header.png" +# html_logo = "_static/powered-by-logo-header.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None +html_favicon = "_static/favicon.ico" # 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'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -html_sidebars = { - "index": [ - "sidebarintro.html", - "links.html", - "sourcelink.html", - "searchbox.html", - ], - "**": [ - "sidebarintro.html", - "links.html", - "sourcelink.html", - "searchbox.html", - ] -} +# html_use_smartypants = True # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'landlab_restdoc' +htmlhelp_basename = "landlab_restdoc" # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "flask": ("https://flask.palletsprojects.com/en/2.2.x/", None), + "landlab": ("https://landlab.readthedocs.io/en/master/", None), +} + # -- Options for todo extension ---------------------------------------------- # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True + +napoleon_numpy_docstring = True +napoleon_google_docstring = False +napoleon_include_init_with_doc = True +napoleon_include_special_with_doc = True + + +# -- Options for towncrier_draft extension -------------------------------------------- + +towncrier_draft_autoversion_mode = "draft" # or: 'sphinx-release', 'sphinx-version' +towncrier_draft_include_empty = True +towncrier_draft_working_directory = pathlib.Path(docs_dir).parent.parent diff --git a/docs/source/contributing/developer_install.rst b/docs/source/contributing/developer_install.rst new file mode 100644 index 0000000..5468b29 --- /dev/null +++ b/docs/source/contributing/developer_install.rst @@ -0,0 +1,58 @@ +.. _developer_install: + +================= +Developer Install +================= + +.. important:: + + The following commands will install *landlab-rest* into your current environment. Although + not necessary, we **highly recommend** you install *landlab-rest* into its own + :ref:`virtual environment `. + +If you will be modifying code or contributing new code to *landlab-rest*, you will first +need to get *landlab-rest*'s source code and then install *landlab-rest* from that code. + +Source Install +-------------- + +*landlab-rest* is actively being developed on GitHub, where the code is freely available. +If you would like to modify or contribute code, you can either clone our +repository + +.. code-block:: bash + + git clone git://github.com/landlab/landlab-rest.git + +or download the `tarball `_ +(a zip file is available for Windows users): + +.. code-block:: bash + + curl -OL https://github.com/landlab/landlab-rest/tarball/master + +Once you have a copy of the source code, you can install it into your current +Python environment, + +.. tab:: mamba + + .. code-block:: bash + + cd landlab-rest + mamba install --file=requirements.txt + pip install -e . + +.. tab:: conda + + .. code-block:: bash + + cd landlab-rest + conda install --file=requirements.txt + pip install -e . + +.. tab:: pip + + .. code-block:: bash + + cd landlab-rest + pip install -e . diff --git a/docs/source/contributing/environments.rst b/docs/source/contributing/environments.rst new file mode 100644 index 0000000..4d203c8 --- /dev/null +++ b/docs/source/contributing/environments.rst @@ -0,0 +1,43 @@ +.. _virtual_environments: + +==================== +Virtual Environments +==================== + +A virtual environment is a self-contained directory tree that contains a Python installation for a particular +version of Python along with additional packages. It solves the problem of one application's +package requirements conflicting with another's. + +Two popular tools used for creating virtual environments are the built-in *venv* module and *conda* +(or the *much* faster and more reliable *mamba*). For virtual environments created using *conda*/*mamba*, +you can use either *conda*/*mamba* or *pip* to install additional packages, while *venv*-created environments +should stick with *pip*. + +.. tab:: mamba + + .. code-block:: bash + + conda install mamba -c conda-forge + mamba create -n landlab-rest + mamba activate landlab-rest + +.. tab:: conda + + .. code-block:: bash + + conda create -n landlab-rest + conda activate landlab-rest + +.. tab:: venv + + .. code-block:: bash + + python -m venv .venv + source .venv/bin/activate + +Note that you will need to activate this environment every time you want to use it in a new shell. + +Helpful links on managing virtual environments: + +* `conda environments `_. +* `venv environments `_. diff --git a/docs/source/contributing/index.rst b/docs/source/contributing/index.rst new file mode 100644 index 0000000..422257c --- /dev/null +++ b/docs/source/contributing/index.rst @@ -0,0 +1,25 @@ +============ +Contributing +============ + +Thank you for your interest in *landlab-rest*! ✨ + +*landlab-rest* is a volunteer maintained open source project, and we welcome contributions +of all forms. This section of *landlab-rest*'s documentation serves as a resource to help +you to contribute to the project. + + +:ref:`Workflow `: + Describes how to work on this project. Start here if you're a new contributor. +:ref:`Developer Install `: + Describes how to install *landlab-rest* when working with the source code. +:ref:`Virtual Environments `: + Describes how to use virtual environments. + +.. toctree:: + :maxdepth: 1 + :hidden: + + workflow + developer_install + environments diff --git a/docs/source/contributing/workflow.rst b/docs/source/contributing/workflow.rst new file mode 100644 index 0000000..0bdc450 --- /dev/null +++ b/docs/source/contributing/workflow.rst @@ -0,0 +1,85 @@ +.. _workflow: + +======== +Workflow +======== + +This page describes the tooling used during development of this project. It also serves +as a reference for the various commands that you would use when working on this project. + +-------- +Overview +-------- + +This project uses the `GitHub Flow`_ for collaboration. The codebase contains primarily Python code. + +- `nox`_ is used for automating development tasks. +- `sphinx-autobuild`_ is used to provide live-reloading pages when working on the docs. +- `pre-commit`_ is used for running the linters. + +------------- +Initial Setup +------------- + +To work on this project, you need to have Python 3.8+. + +* Clone this project using git: + + .. code:: bash + + git clone https://github.com/landlab/landlab-rest.git + cd landlab-rest + +* Install the project's development workflow runner: + + .. code:: bash + + pip install nox + +You're all set for working on this project. + +-------- +Commands +-------- + +Code Linting +============ + +.. code:: bash + + nox -s lint + + +Run the linters, as configured with `pre-commit`_. + +Local Development Server +======================== + +.. code:: bash + + nox -s docs-live + + +Serve this project's documentation locally, using `sphinx-autobuild`_. This will open +the generated documentation page in your browser. + +The server also watches for changes made to the documentation (`docs/source`), which +will trigger a rebuild. Once the build is completed, the server will +reload any open pages using *livereload*. + +Documentation Generation +======================== + +.. code:: bash + + nox -s docs + +Generate the documentation for *landlab-rest* into the `docs/build` folder. This (mostly) +does the same thing as ``nox -s docs-live``, except it invokes ``sphinx-build`` instead +of `sphinx-autobuild`_. + + +.. _GitHub Flow: https://guides.github.com/introduction/flow/ +.. _nox: https://nox.readthedocs.io/en/stable/ +.. _sphinx-autobuild: https://github.com/executablebooks/sphinx-autobuild +.. _pre-commit: https://pre-commit.com/ diff --git a/docs/source/environment.yml b/docs/source/environment.yml deleted file mode 100644 index 4663ae6..0000000 --- a/docs/source/environment.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: landlab_rest_docs -channels: - - conda-forge -dependencies: -- python==3.6 -- pandoc -- pip -- sphinx>=1.5.1 -- sphinx_rtd_theme -- tornado -- entrypoints -- pip: - - sphinxcontrib_github_alt diff --git a/docs/source/index.rst b/docs/source/index.rst index 042d05a..e025ab1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,20 +1,26 @@ -.. landlab_rest documentation master file, created by - sphinx-quickstart on Wed Apr 17 14:11:40 2019. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +============ +landlab REST +============ -Welcome to landlab_rest's documentation! -======================================== +.. include:: ../../README.rst + :start-after: .. start-intro + :end-before: .. end-intro .. toctree:: - :maxdepth: 2 - :caption: Contents: + :maxdepth: 1 + :hidden: + quickstart + starting + requests +.. toctree:: + :caption: Development + :maxdepth: 1 + :hidden: -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + contributing/index + Reference + changes + Contributors + License diff --git a/docs/source/license.rst b/docs/source/license.rst new file mode 100644 index 0000000..2f7644a --- /dev/null +++ b/docs/source/license.rst @@ -0,0 +1 @@ +.. include:: ../../LICENSE.rst diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst new file mode 100644 index 0000000..ea9fce4 --- /dev/null +++ b/docs/source/quickstart.rst @@ -0,0 +1,17 @@ +========== +Quickstart +========== + +.. important:: + + The following commands will install *landlab-rest* into your current environment. Although + not necessary, we **highly recommend** you install *landlab-rest* into its own + :ref:`virtual environment `. + +.. include:: ../../README.rst + :start-after: .. start-quickstart + :end-before: .. end-quickstart + +If you would like the very latest development version of *landlab-rest* or want to modify +or contribute code to the *landlab-rest* project, you will need to do a +:ref:`developer installation ` of *landlab-rest* from source. diff --git a/docs/source/requests.rst b/docs/source/requests.rst new file mode 100644 index 0000000..54cf187 --- /dev/null +++ b/docs/source/requests.rst @@ -0,0 +1,68 @@ +================ +Sending requests +================ + +For a list of supported graphs + +.. code:: bash + + curl https://0.0.0.0:8080/graphs/ + +A uniform rectilinear graph (i.e. a raster), + +.. code:: bash + + curl https://0.0.0.0:8080/graphs/raster + +You can pass parameters like, + +.. code:: bash + + curl 'https://0.0.0.0:8080/graphs/raster?shape=4,5&spacing=2.,1.' + +---------- +Graphs API +---------- + + +GET /graphs +=========== + +List graphs. + +:Description: List the names of all the supported *Landlab* graphs. +:Status Codes: + * 200 OK – An array of strings. +:Examples: + + .. code-block:: bash + + curl 'https://0.0.0.0:8080/graphs/' + +GET /graphs/{name} +================== + +Get the graph structure. + +:Description: Get the *x* and *y* positions of the elements of a graph along + with their connectivity. +:Parameters: + * name (string) - A name of a *Landlab* graph. +:Query Parameters: + * shape (string) - The number of rows and columns + * spacing (string) - The spacing between columns and rows + * origin (string) - The *x* and *y* position of the lower-left of the grid +:Status Codes: + * 200 OK – A Graph object. +:Examples: + + .. code-block:: bash + + curl 'https://0.0.0.0:8080/graphs/raster' + + A raster grid with 4 rows and 5 columns or nodes with a columns spacing of 2.0 + and a row spacing of 1.0. + + .. code-block:: bash + + curl 'https://0.0.0.0:8080/graphs/raster?shape=4,5&spacing=2.,1.' diff --git a/docs/source/starting.rst b/docs/source/starting.rst new file mode 100644 index 0000000..d719d84 --- /dev/null +++ b/docs/source/starting.rst @@ -0,0 +1,20 @@ +=================== +Starting the server +=================== + +Use the ``start-sketchbook`` command to start running a *landlab-rest* server, + +.. code:: + + start-sketchbook + +Look at the line containing `Serving on` to see what host and port the +server is running on. Alternatively, you can use the `--host` and `--port` +options to specify a specific host and port (``--help`` for help). + +Once running, you should now be able to send requests to the server. For instance, +to get a `RasterModelGrid`, + +.. code:: + + curl https://0.0.0.0:8080/graphs/raster diff --git a/env.yaml b/env.yaml index 0db9e03..d3c1241 100644 --- a/env.yaml +++ b/env.yaml @@ -3,4 +3,4 @@ channels: - conda-forge dependencies: - python - - landlab \ No newline at end of file + - landlab diff --git a/landlab_rest/__init__.py b/landlab_rest/__init__.py index 2bc80f0..b069443 100644 --- a/landlab_rest/__init__.py +++ b/landlab_rest/__init__.py @@ -1,3 +1,5 @@ +"""A (kind of) RESTful interface to Landlab graphs.""" + from ._version import __version__ from .app import create_app diff --git a/landlab_rest/api/__init__.py b/landlab_rest/api/__init__.py index e69de29..3c10bba 100644 --- a/landlab_rest/api/__init__.py +++ b/landlab_rest/api/__init__.py @@ -0,0 +1 @@ +"""The Web API for landlab-rest.""" diff --git a/landlab_rest/api/graphs.py b/landlab_rest/api/graphs.py index 7057fad..61271c3 100644 --- a/landlab_rest/api/graphs.py +++ b/landlab_rest/api/graphs.py @@ -1,3 +1,4 @@ +"""Define the API for /graphs/{graph}.""" import json import urllib @@ -8,6 +9,18 @@ def as_resource(resp): + """Convert a response to a resource. + + Parameters + ---------- + resp : object + The object to convert to a resource. + + Returns + ------- + :class:`~flask.Response` + The object as a resource. + """ return Response( json.dumps(resp, sort_keys=True, indent=2, separators=(",", ": ")), mimetype="application/x-resource+json; charset=utf-8", @@ -15,6 +28,18 @@ def as_resource(resp): def as_collection(resp): + """Convert a response to a collection of resources. + + Parameters + ---------- + resp : object + The objects to convert to resources. + + Returns + ------- + :class:`~flask.Response` + The object as a resource collection. + """ return Response( json.dumps(resp, sort_keys=True, indent=2, separators=(",", ": ")), mimetype="application/x-collection+json; charset=utf-8", @@ -22,6 +47,18 @@ def as_collection(resp): def jsonify_collection(items): + """Convert an iterable to json. + + Parameters + ---------- + items : iterable + The items to convert to json. + + Returns + ------- + :class:`~flask.Response` + The items converted to json. + """ collection = [] for item in items: collection.append(item.to_resource()) @@ -32,10 +69,38 @@ def jsonify_collection(items): def to_resource(grid, href=None, repr_=None): + """Convert a landlab graph to a resource object. + + Parameters + ---------- + grid : :class:`~landlab.grid.base.ModelGrid` + A Landlab Graph. + href : str, optional + Path to the resource. + repr_ : str, optional + Printable representation of the graph. + + Returns + ------- + dict + The object expressed as a resource. + """ return {"_type": "graph", "href": href, "graph": grid_as_dict(grid), "repr": repr_} def grid_as_dict(grid): + """Express a Landlab Graph as a Python dict. + + Parameters + ---------- + grid : :class:`~landlab.grid.base.ModelGrid` + A Landlab grid. + + Returns + ------- + dict + The graph expressed as a Python dictionary. + """ grid.ds.update( { "corner": grid.corners.reshape(-1), @@ -50,13 +115,14 @@ def grid_as_dict(grid): @graphs_page.route("/") def show(): - graphs = ["hex", "raster", "radial"] - graphs.sort() + """Show available graphs.""" + graphs = sorted(["hex", "raster", "radial"]) return jsonify(graphs) @graphs_page.route("/raster") def raster(): + """Express a 2D RasterGraph as a resource.""" args = dict( shape=request.args.get("shape", "3,3"), spacing=request.args.get("spacing", "1.0,1.0"), @@ -85,6 +151,7 @@ def raster(): @graphs_page.route("/hex") def hex(): + """Express a 2D HexGraph as a resource.""" args = dict( shape=request.args.get("shape", "4,4"), spacing=request.args.get("spacing", "1.0"), @@ -126,6 +193,7 @@ def hex(): @graphs_page.route("/radial") def radial(): + """Express a 2D RadialGraph as a resource.""" args = dict( shape=request.args.get("shape", "3,4"), spacing=request.args.get("spacing", "1.0"), diff --git a/landlab_rest/app.py b/landlab_rest/app.py index b3760d4..0ad98ca 100644 --- a/landlab_rest/app.py +++ b/landlab_rest/app.py @@ -1,15 +1,16 @@ +"""Create the Flask app for *landlab-rest*.""" import importlib -from flask_cors import CORS - from flask import Blueprint, Flask, jsonify, url_for +from flask_cors import CORS def register_blueprints(app): + """Register the *landlab_rest* API.""" rv = [] for name in ["graphs"]: - m = importlib.import_module(".api.{bp}".format(bp=name), package="landlab_rest") + m = importlib.import_module(f".api.{name}", package="landlab_rest") for item in dir(m): item = getattr(m, item) if isinstance(item, Blueprint): @@ -20,6 +21,7 @@ def register_blueprints(app): def create_app(): + """Create the Flask app.""" app = Flask(__name__, instance_relative_config=True) CORS(app) diff --git a/landlab_rest/cli.py b/landlab_rest/cli.py index e1af611..f9e2b43 100644 --- a/landlab_rest/cli.py +++ b/landlab_rest/cli.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +"""Command line interface for starting the webservice.""" import click from .start import start @@ -13,6 +13,7 @@ @click.option("--ssl-chain", default=None, help="path to host SSL certificate chain") @click.option("--silent", is_flag=True, help="only emit messages on error") def main(host, port, ssl_cert, ssl_key, ssl_chain, silent): + """Start the landlab-rest webservice.""" if not silent: - click.secho("🚀 launching landlab sketchbook on {0}:{1}".format(host, port)) + click.secho(f"🚀 launching landlab sketchbook on {host}:{port}") start(host, port, ssl_cert, ssl_key, ssl_chain) diff --git a/landlab_rest/start.py b/landlab_rest/start.py index 9c95f55..815ae15 100644 --- a/landlab_rest/start.py +++ b/landlab_rest/start.py @@ -1,11 +1,13 @@ +"""Start the application.""" import cherrypy + from landlab_rest import create_app app = create_app() def start(host, port, ssl_cert, ssl_key, ssl_chain): - + """Start the web application.""" # Mount the application cherrypy.tree.graft(app, "/") diff --git a/news/10.feature b/news/10.feature index 3f47f3a..0bebfdc 100644 --- a/news/10.feature +++ b/news/10.feature @@ -1,5 +1,3 @@ Added commandline options for specifying ssl parameters. New options are ``--ssl-cert``, ``--ssl-key``, and ``--ssl-chain`` that give paths to various ssl files. - - diff --git a/news/11.bugfix b/news/11.bugfix index 4fdd504..85afc88 100644 --- a/news/11.bugfix +++ b/news/11.bugfix @@ -1,2 +1 @@ Updated *landlab-rest* to work with, and only with, *landlab* v2. - diff --git a/news/12.misc b/news/12.misc index c06b781..b3e5f72 100644 --- a/news/12.misc +++ b/news/12.misc @@ -1,2 +1 @@ Move package metadata into *pyproject.toml*. - diff --git a/news/12.misc.1 b/news/12.misc.1 index f2bff6a..db02129 100644 --- a/news/12.misc.1 +++ b/news/12.misc.1 @@ -1,2 +1 @@ Fixed unit tests for *landlab* v2. - diff --git a/news/12.misc.2 b/news/12.misc.2 index 1c0cf46..655f096 100644 --- a/news/12.misc.2 +++ b/news/12.misc.2 @@ -1,2 +1 @@ Set up *towncrier* to manage the changelog. - diff --git a/news/13.misc b/news/13.misc index aadbf23..e5f1e51 100644 --- a/news/13.misc +++ b/news/13.misc @@ -1,2 +1 @@ Updated the Dockerfile to use micromamba - diff --git a/news/14.misc b/news/14.misc index 87e78ca..3191cb4 100644 --- a/news/14.misc +++ b/news/14.misc @@ -1,2 +1 @@ Setup continuous integration to run using GitHub actions instead of Travis-CI. - diff --git a/news/15.docs b/news/15.docs index ef1deb6..0010a9b 100644 --- a/news/15.docs +++ b/news/15.docs @@ -1,2 +1 @@ Added badges to the README file. - diff --git a/news/16.docs b/news/16.docs new file mode 100644 index 0000000..b0a96e6 --- /dev/null +++ b/news/16.docs @@ -0,0 +1,2 @@ + +Set up a sphinx-based documentation website. diff --git a/news/16.misc.1 b/news/16.misc.1 new file mode 100644 index 0000000..f9a1bf2 --- /dev/null +++ b/news/16.misc.1 @@ -0,0 +1,2 @@ + +Added a *nox* file to automatically run routine project development tasks. diff --git a/news/16.misc.2 b/news/16.misc.2 new file mode 100644 index 0000000..e279c63 --- /dev/null +++ b/news/16.misc.2 @@ -0,0 +1,2 @@ + +Added lots of pre-commit hooks for linting. diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..647f9a9 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,217 @@ +import os +import pathlib +import shutil + +import nox + +PROJECT = "landlab_rest" +ROOT = pathlib.Path(__file__).parent + + +@nox.session +def test(session: nox.Session) -> None: + """Run the tests.""" + session.install("-e", ".[dev]") + + args = session.posargs or ["-n", "auto", "--cov", PROJECT, "-vvv"] + if "CI" in os.environ: + args.append("--cov-report=xml:$(pwd)/coverage.xml") + session.run("pytest", *args) + # "--fail-under=100", + + +@nox.session(name="test-cli") +def test_cli(session: nox.Session) -> None: + """Test the command line interface.""" + session.install(".") + session.run("start-sketchbook", "--help") + session.run("start-sketchbook", "--version") + + +@nox.session(reuse_venv=True) +def lint(session: nox.Session) -> None: + """Look for lint.""" + session.install("pre-commit") + + args = list(session.posargs) + args.append("--all-files") + if "CI" in os.environ: + args.append("--show-diff-on-failure") + + session.run("pre-commit", "run", *args) + + # towncrier(session) + + +@nox.session +def towncrier(session: nox.Session) -> None: + """Check that there is a news fragment.""" + session.install("towncrier") + session.run("towncrier", "check", "--compare-with", "origin/master") + + +@nox.session(name="build-docs", reuse_venv=True) +def build_docs(session: nox.Session) -> None: + """Build the docs.""" + with session.chdir(ROOT): + session.install(".[docs]") + + clean_docs(session) + + with session.chdir(ROOT): + session.run( + "sphinx-apidoc", + "-e", + "-force", + "--no-toc", + "-o", + "docs/source/api", + "landlab_rest", + ) + session.run( + "sphinx-build", + "-b", + "html", + "-W", + "docs/source", + "docs/build/html", + ) + + +@nox.session(name="live-docs", reuse_venv=True) +def live_docs(session: nox.Session) -> None: + session.install(".[docs]") + session.run( + "sphinx-autobuild", + "-b", + "html", + "docs/source", + "docs/build/html", + "--open-browser", + ) + + +@nox.session(name="build-requirements", reuse_venv=True) +def build_requirements(session: nox.Session) -> None: + """Create requirements files from pyproject.toml.""" + session.install("tomli") + + with open("requirements.txt", "w") as fp: + session.run("python", "requirements.py", stdout=fp) + + for extra in ["dev", "docs"]: + with open(f"requirements-{extra}.txt", "w") as fp: + session.run("python", "requirements.py", extra, stdout=fp) + + +@nox.session +def build(session: nox.Session) -> None: + """Build sdist and wheel dists.""" + session.install("pip") + session.install("build") + session.run("python", "--version") + session.run("pip", "--version") + session.run("python", "-m", "build", "--outdir", "./wheelhouse") + + +@nox.session +def release(session): + """Tag, build and publish a new release to PyPI.""" + session.install("zest.releaser[recommended]") + session.install("zestreleaser.towncrier") + session.run("fullrelease") + + +@nox.session +def publish_testpypi(session): + """Publish wheelhouse/* to TestPyPI.""" + session.run("twine", "check", "wheelhouse/*") + session.run( + "twine", + "upload", + "--skip-existing", + "--repository-url", + "https://test.pypi.org/legacy/", + "wheelhouse/*.tar.gz", + ) + + +@nox.session +def publish_pypi(session): + """Publish wheelhouse/* to PyPI.""" + session.run("twine", "check", "wheelhouse/*") + session.run( + "twine", + "upload", + "--skip-existing", + "wheelhouse/*.tar.gz", + ) + + +@nox.session(python=False) +def clean(session): + """Remove all .venv's, build files and caches in the directory.""" + for folder in _args_to_folders(session.posargs): + with session.chdir(folder): + + shutil.rmtree("build", ignore_errors=True) + shutil.rmtree("wheelhouse", ignore_errors=True) + shutil.rmtree(f"{PROJECT}.egg-info", ignore_errors=True) + shutil.rmtree(".pytest_cache", ignore_errors=True) + shutil.rmtree(".venv", ignore_errors=True) + + for pattern in ["*.py[co]", "__pycache__"]: + _clean_rglob(pattern) + + +@nox.session(python=False, name="clean-checkpoints") +def clean_checkpoints(session): + """Remove jupyter notebook checkpoint files.""" + for folder in _args_to_folders(session.posargs): + with session.chdir(folder): + _clean_rglob("*-checkpoint.ipynb") + _clean_rglob(".ipynb_checkpoints") + + +@nox.session(python=False, name="clean-docs") +def clean_docs(session: nox.Session) -> None: + """Clean up the docs folder.""" + with session.chdir(ROOT / "docs"): + if os.path.exists("build"): + shutil.rmtree("build") + + for p in pathlib.Path("source/api").rglob("landlab_rest*.rst"): + p.unlink() + + +@nox.session(python=False, name="clean-ext") +def clean_ext(session: nox.Session) -> None: + """Clean shared libraries for extension modules.""" + for folder in _args_to_folders(session.posargs): + with session.chdir(folder): + _clean_rglob("*.so") + + +@nox.session(python=False) +def nuke(session): + """Run all clean sessions.""" + clean_checkpoints(session) + clean_docs(session) + clean(session) + clean_ext(session) + + +def _args_to_folders(args): + return [ROOT] if not args else [pathlib.Path(f) for f in args] + + +def _clean_rglob(pattern): + nox_dir = pathlib.Path(".nox") + + for p in pathlib.Path(".").rglob(pattern): + if nox_dir in p.parents: + continue + if p.is_dir(): + p.rmdir() + else: + p.unlink() diff --git a/pyproject.toml b/pyproject.toml index d0451bb..3f4ea0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Scientific/Engineering :: Physics", + "Topic :: Scientific/Engineering :: Physics", ] requires-python = ">=3.8" dependencies = [ @@ -56,12 +56,22 @@ dev = [ "flake8", "flake8-bugbear", "isort", + "nox", "pre-commit", "pytest", "pytest-cov", + "pytest-xdist", "towncrier", "zest.releaser[recommended]", ] +docs = [ + "sphinx>=4", + "furo", + "sphinxcontrib.towncrier", + "sphinx-copybutton", + "sphinx-inline-tabs", + "towncrier", +] [project.scripts] start-sketchbook = "landlab_rest.cli:main" diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..3537dac --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,14 @@ +# Requirements extracted from pyproject.toml +# [project.optional-dependencies] dev +black +coveralls +flake8 +flake8-bugbear +isort +nox +pre-commit +pytest +pytest-cov +pytest-xdist +towncrier +zest.releaser[recommended] diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..9706633 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,8 @@ +# Requirements extracted from pyproject.toml +# [project.optional-dependencies] docs +furo +sphinx-copybutton +sphinx-inline-tabs +sphinx>=4 +sphinxcontrib.towncrier +towncrier diff --git a/setup.cfg b/setup.cfg index da89ce2..035115d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ universal = 1 [flake8] exclude = docs -ignore = +ignore = E203 E501 W503 diff --git a/tests/test_hex.py b/tests/test_hex.py index 090eb53..ec51b48 100644 --- a/tests/test_hex.py +++ b/tests/test_hex.py @@ -57,7 +57,7 @@ def test_hex_default_origin(client): @pytest.mark.parametrize("x0", (-1.0, 1.0, 2.0, 4.0)) def test_hex_origin(client, x0, y0, orientation, node_layout): query = dict( - origin="{0},{1}".format(y0, x0), + origin=f"{y0},{x0}", orientation=orientation, node_layout=node_layout, ) diff --git a/tests/test_radial.py b/tests/test_radial.py index 3b8850a..ac408ab 100644 --- a/tests/test_radial.py +++ b/tests/test_radial.py @@ -52,7 +52,7 @@ def test_radial_default_origin(client): @pytest.mark.parametrize("spacing", (0.5, 1.0, 2.0, 4.0)) def test_radial_spacing(client, spacing): - url = "/graphs/radial?spacing={0}".format(spacing) + url = f"/graphs/radial?spacing={spacing}" graph = xr.Dataset.from_dict(client.get(url).get_json()["graph"]) unit = xr.Dataset.from_dict( client.get("/graphs/radial?spacing=1.0").get_json()["graph"] @@ -68,7 +68,7 @@ def test_radial_spacing(client, spacing): @pytest.mark.parametrize("y0", (-1.0, 1.0, 2.0, 4.0)) @pytest.mark.parametrize("x0", (-1.0, 1.0, 2.0, 4.0)) def test_radial_origin(client, x0, y0): - url = "/graphs/radial?origin={0},{1}".format(y0, x0) + url = f"/graphs/radial?origin={y0},{x0}" graph = xr.Dataset.from_dict(client.get(url).get_json()["graph"]) centered = xr.Dataset.from_dict( client.get("/graphs/radial?origin=0.0,0.0").get_json()["graph"] diff --git a/tests/test_raster.py b/tests/test_raster.py index 2edb665..08d9c58 100644 --- a/tests/test_raster.py +++ b/tests/test_raster.py @@ -7,7 +7,7 @@ @pytest.mark.parametrize("graph_type", ("hex", "radial", "raster")) def test_graph_data(client, graph_type): graph = xr.Dataset.from_dict( - client.get("/graphs/{0}".format(graph_type)).get_json()["graph"] + client.get(f"/graphs/{graph_type}").get_json()["graph"] ) assert set(graph.dims) == { @@ -113,7 +113,7 @@ def test_raster_default_origin(client): @pytest.mark.parametrize("n_cols", (4, 8, 16, 32)) @pytest.mark.parametrize("n_rows", (4, 8, 16, 32)) def test_raster_shape(client, n_rows, n_cols): - url = "/graphs/raster?shape={0},{1}".format(n_rows, n_cols) + url = f"/graphs/raster?shape={n_rows},{n_cols}" graph = xr.Dataset.from_dict(client.get(url).get_json()["graph"]) assert graph.dims["node"] == n_rows * n_cols @@ -121,7 +121,7 @@ def test_raster_shape(client, n_rows, n_cols): @pytest.mark.parametrize("dy", (1.0, 2.0, 4.0)) @pytest.mark.parametrize("dx", (1.0, 2.0, 4.0)) def test_raster_spacing(client, dx, dy): - url = "/graphs/raster?spacing={0},{1}".format(dy, dx) + url = f"/graphs/raster?spacing={dy},{dx}" graph = xr.Dataset.from_dict(client.get(url).get_json()["graph"]) expected_x, expected_y = np.meshgrid([0.0, 1.0, 2.0], [0.0, 1.0, 2.0]) @@ -133,7 +133,7 @@ def test_raster_spacing(client, dx, dy): @pytest.mark.parametrize("y0", (-1.0, 1.0, 2.0, 4.0)) @pytest.mark.parametrize("x0", (-1.0, 1.0, 2.0, 4.0)) def test_raster_origin(client, x0, y0): - url = "/graphs/raster?origin={0},{1}".format(y0, x0) + url = f"/graphs/raster?origin={y0},{x0}" graph = xr.Dataset.from_dict(client.get(url).get_json()["graph"]) expected_x, expected_y = np.meshgrid([0.0, 1.0, 2.0], [0.0, 1.0, 2.0]) diff --git a/tests/test_response.py b/tests/test_response.py index bd39e09..69b59c6 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -5,13 +5,13 @@ @pytest.mark.parametrize("graph_type", ("hex", "radial", "raster")) def test_response_status(client, graph_type): - response = client.get("/graphs/{0}".format(graph_type)) + response = client.get(f"/graphs/{graph_type}") assert response.status_code == 200 @pytest.mark.parametrize("graph_type", ("hex", "radial", "raster")) def test_response_type(client, graph_type): - response = client.get("/graphs/{0}".format(graph_type)) + response = client.get(f"/graphs/{graph_type}") data = response.get_json() assert data["_type"] == "graph" assert "graph" in data @@ -19,13 +19,13 @@ def test_response_type(client, graph_type): @pytest.mark.parametrize("graph_type", ("hex", "radial", "raster")) def test_response_href(client, graph_type): - response = client.get("/graphs/{0}".format(graph_type)) + response = client.get(f"/graphs/{graph_type}") data = response.get_json() parts = urllib.parse.urlsplit(data["href"]) assert parts[0] == "" assert parts[1] == "" - assert parts[2] == "/graphs/{0}".format(graph_type) + assert parts[2] == f"/graphs/{graph_type}" assert urllib.parse.parse_qs(parts[3]) assert parts[4] == "" @@ -39,7 +39,7 @@ def test_response_href(client, graph_type): ), ) def test_response_repr(client, graph_type, expected): - response = client.get("/graphs/{0}".format(graph_type)) + response = client.get(f"/graphs/{graph_type}") data = response.get_json() assert data["repr"].startswith(expected)