diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c7d8d38 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2019, Brookhaven National Laboratory +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 9440067..f763fed 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# diffpy-cookie-cutter -A cookie-cutter for DiffPy packages. +# diffpy-cookiecutter +a cookiecutter for diffpy packages diff --git a/RELEASES.rst b/RELEASES.rst new file mode 100644 index 0000000..d0a3e12 --- /dev/null +++ b/RELEASES.rst @@ -0,0 +1,12 @@ +=============== +Release History +=============== + +v0.1.1 (2021-10-26) +................... ++ relax python version for pre-commit + + +v0.1.0 (2021-10-08) +................... +Initial Release diff --git a/cookiecutter.json b/cookiecutter.json index d197c01..6051be6 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -3,10 +3,12 @@ "email": "", "github_username": "", "project_name": "Your Project Name", + "project_slug": "{{ cookiecutter.project_name|replace('.', '')|lower }}", + "project_short_name": "", "package_dist_name": "{{ cookiecutter.project_name|replace(' ', '-')|lower }}", "package_dir_name": "{{ cookiecutter.project_name|replace(' ', '_')|replace('-', '_')|lower }}", "repo_name": "{{ cookiecutter.project_name|replace(' ', '-')|lower }}", - "project_short_description": "Python package for doing science.", + "project_short_description": "", "minimum_supported_python_version": ["3.8", "3.9", "3.10"], "_copy_without_render": [ "*.html", diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..b4a5893 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = "-W" # This flag turns warnings into errors. +SPHINXBUILD = sphinx-build +SPHINXPROJ = PackagingScientificPython +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..ac53d5b --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=PackagingScientificPython + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/source/_static/.placeholder b/docs/source/_static/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/docs/source/advanced-testing.rst b/docs/source/advanced-testing.rst new file mode 100644 index 0000000..04b0bfa --- /dev/null +++ b/docs/source/advanced-testing.rst @@ -0,0 +1,155 @@ +========================= +Common Patterns for Tests +========================= + +In this section you will learn some useful features of pytest that can make +your tests succinct and easy to maintain. + +Parametrized Tests +------------------ + +Tests that apply the same general test logic to a collection of different +parameters can use parametrized tests. For example, this: + +.. code-block:: python + + import numpy as np + from ..refraction import snell + + + def test_perpendicular(): + # For any indexes, a ray normal to the surface should not bend. + # We'll try a couple different combinations of indexes.... + + actual = snell(0, 2.00, 3.00) + expected = 0 + assert actual == expected + + actual = snell(0, 3.00, 2.00) + expected = 0 + assert actual == expected + +can be rewritten as: + +.. code-block:: python + + import numpy as np + import pytest + from ..refraction import snell + + + @pytest.mark.parametrize('n1, n2', + [(2.00, 3.00), + (3.00, 2.00), + ]) + def test_perpendicular(n1, n2): + # For any indexes, a ray normal to the surface should not bend. + # We'll try a couple different combinations of indexes.... + + actual = snell(0, n1, n2) + expected = 0 + assert actual == expected + +The string ``'n1, n2'`` specifies which parameters this decorator will fill in. +Pytest will run ``test_perpendicular`` twice, one for each entry in the +list ``[(2.00, 3.00), (3.00, 2.00)]``, passing in the respective values ``n1`` +and ``n2`` as arguments. + +From here we refer you to the +`pytest parametrize documentation `_. + +Fixtures +-------- + +Tests that have different logic but share the same setup code can use pytest +fixtures. For example, this: + +.. code-block:: python + + import numpy as np + + + def test_height(): + # Construct a 1-dimensional Gaussian peak. + x = np.linspace(-10, 10, num=21) + sigma = 3.0 + peak = np.exp(-(x / sigma)**2 / 2) / (sigma * np.sqrt(2 * np.pi)) + expected = 1 / (sigma * np.sqrt(2 * np.pi)) + # Test that the peak height is correct. + actual = np.max(peak) + assert np.allclose(actual, expected) + + + def test_nonnegative(): + # Construct a 1-dimensional Gaussian peak. + x = np.linspace(-10, 10, num=20) + sigma = 3.0 + peak = np.exp(-(x / sigma)**2 / 2) / (sigma * np.sqrt(2 * np.pi)) + # Test that there are no negative values. + assert np.all(peak >= 0) + +can be written as: + +.. code-block:: python + + import pytest + import numpy as np + + + @pytest.fixture + def peak(): + # Construct a 1-dimensional Gaussian peak. + x = np.linspace(-10, 10, num=21) + sigma = 3.0 + peak = np.exp(-(x / sigma)**2 / 2) / (sigma * np.sqrt(2 * np.pi)) + return peak + + + def test_height(peak): + expected = 1 / (sigma * np.sqrt(2 * np.pi)) + # Test that the peak height is correct. + actual = np.max(peak) + assert np.allclose(actual, expected) + + + def test_nonnegative(peak): + # Test that there are no negative values. + assert np.all(peak >= 0) + +To reuse a fixture in multiple files, add it to ``conftest.py`` located in the +``tests/`` directory. It will automatically be imported by pytest into each +test module. + +From here we refer you to the +`pytest fixtures documentation `_. + +Skipping Tests +-------------- + +Sometimes it is useful to skip specific tests under certain conditions. +Examples: + +.. code-block:: python + + import pytest + import sys + + + @pytest.mark.skipif(sys.version_info < (3, 7), + reason="requires python3.7 or higher") + def test_something(): + ... + + + @pytest.mark.skipif(sys.platform == 'win32', + reason="does not run on windows") + def test_something_that_does_not_work_on_windows(): + ... + + + def test_something_that_needs_a_special_dependency(): + some_library = pytest.importorskip("some_library") + ... + +From here we refer you to the +`pytest skipping documentation `_. diff --git a/docs/source/ci.rst b/docs/source/ci.rst new file mode 100644 index 0000000..dca1160 --- /dev/null +++ b/docs/source/ci.rst @@ -0,0 +1,63 @@ +============================= +Continous Integration Testing +============================= + +In this section you will: + +* Understand the benefits Continuous Integration. +* Configure Travis-CI, a "continuous integration" service, to operate on your + GitHub repository. + +What is CI for? +--------------- + +If "Continuous Integration" (CI) is new to you, we refer you to +`this excellent Software Carpentry tutorial `_ +on the subject. To summarize, CI speeds development by checking out your code on +a fresh, clean server, installing your software, running the tests, and +reporting the results. This helps you ensure that your code will work on your +colleague's computer---that it doesn't accidentally depend on some local detail +of your machine. It also creates a clear, public record of whether the tests +passed or failed, so if things are accidentally broken (say, while you are on +vacation) you can trace when the breaking change occurred. + +Travis-CI Configuration +----------------------- + +The cookiecutter template has already generated a configuration file for +Travis-CI, which is one of several CI services that are free for public +open-source projects. + +.. literalinclude:: example_travis.yml + +You can customize this to your liking. For example, if you are migrating a +large amount of existing code that is not compliant with PEP8, you may want to +remove the line that does ``flake8`` style-checking. + +Activate Travis-CI for Your GitHub Repository +--------------------------------------------- + +#. Go to https://travis-ci.org and sign in with your GitHub account. +#. You will be prompted to authorize Travis-CI to access your GitHub account. + Authorize it. +#. You will be redirected to https://travis-ci.org/profile, which shows a list + of your GitHub repositories. If necessary, click the "Sync Account" button + to refresh that list. +#. Find your new repository in the list. Click the on/off switch next to its + name activate Travis-CI on that repository. +#. Click the repository name, which will direct you to the list of *builds* at + ``https://travis-ci.org/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME/builds``. The + list will currently be empty. You'll see construction cones. +#. The next time you open a pull request or push a new commit to the master + branch, Travis-CI will kick off a new build, and that list will update. + +.. note:: + + If this repository belongs to a GitHub *organization* (e.g. + http://github.com/NSLS-II) as opposed to a personal user account + (e.g. http://github.com/danielballan) you should follow Steps 3-5 + above for the organization's profile at + ``https://travis-ci.org/profile/YOUR_GITHUB_ORGANIZATION``. It does no + harm to *also* activate Travis-CI for your personal fork at + ``https://travis-ci.org/profile``, but it's more important to activate it for + the upstream fork associated with the organization. diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..d8fda09 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Scientific Python Cookiecutter documentation build configuration file, created by +# sphinx-quickstart on Thu Jun 28 12:35:56 2018. +# +# 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 sys +sys.path.insert(0, os.path.abspath('vendor')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.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.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.githubpages', + 'sphinx.ext.intersphinx', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode', + 'youtube', + 'IPython.sphinxext.ipython_directive', + 'IPython.sphinxext.ipython_console_highlighting', + 'matplotlib.sphinxext.plot_directive', + 'numpydoc', + 'sphinx_copybutton', +] + +# Configuration options for plot_directive. See: +# https://github.com/matplotlib/matplotlib/blob/f3ed922d935751e08494e5fb5311d3050a3b637b/lib/matplotlib/sphinxext/plot_directive.py#L81 +plot_html_show_source_link = False +plot_html_show_formats = False + +# Generate the API documentation when building +autosummary_generate = True +numpydoc_show_class_members = False + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# 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 = 'Scientific Python Cookiecutter' +copyright = '2018, Contributors' +author = 'Contributors' + +# 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 = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# 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 = 'en' + +# 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 = [] + +# 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 = 'sphinx_rtd_theme' +import sphinx_rtd_theme +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# 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'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ScientificPythonCookiecutterdoc' + + +# -- 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', +} + +# 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, 'ScientificPythonCookiecutter.tex', 'Scientific Python Cookiecutter Documentation', + 'Contributors', '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, 'scientificpythoncookiecutter', 'Scientific Python Cookiecutter Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# 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, 'ScientificPythonCookiecutter', 'Scientific Python Cookiecutter Documentation', + author, 'ScientificPythonCookiecutter', 'One line description of project.', + 'Miscellaneous'), +] + + + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'python': ('https://docs.python.org/3/', None), + 'numpy': ('https://docs.scipy.org/doc/numpy/', None), + 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None), + 'pandas': ('https://pandas.pydata.org/pandas-docs/stable', None), + 'matplotlib': ('https://matplotlib.org', None), +} diff --git a/docs/source/environments.rst b/docs/source/environments.rst new file mode 100644 index 0000000..2cbd18b --- /dev/null +++ b/docs/source/environments.rst @@ -0,0 +1,65 @@ +================================= +Environments and Package Managers +================================= + +Throughout this tutorial, we used ``pip``, a Python package manager, to install +our software and other Python software we needed. In :doc:`preliminaries`, we +used ``venv`` to create a "virtual environment", a sandbox separate from the +general system Python where we could install and use software without +interfering with any system Python tools or other projects. + +There are many alternatives to ``venv``. +`This post of Stack Overflow `_ +is a good survey of the field. + +Conda +----- + +Conda, which is both a package manager (like ``pip``) and an virtual +environment manager (like ``venv``) was developed specifically for the +scientific Python community. Conda is not limited to Python packages: it has +grown into a general-purpose user-space package manager. Many important +scientific Python packages have important dependencies that aren't Python +packages and can't be installed using ``pip``. Before conda, the user had to +sort out how to install these extra dependencies using the system package +manager, and it was a common pain point. Conda can install all of the +dependencies. + +Conda is conceptually a heavier lift that ``pip`` and ``venv``, which is why we +still with the basics in :doc:`preliminaries`, but it is extremely popular +among both beginners and experts, and we recommend becoming familiar with it. + +Check whether ``conda`` is already installed: + +.. code-block:: bash + + conda + +If that is not found, follow the steps in the +`conda installation guide `_, +which has instructions for Windows, OSX, and Linux. It offers both miniconda (a +minimal installation) and Anaconda. For our purposes, either is suitable. +Miniconda is a faster installation. + +Now, create an environment. + +.. code-block:: bash + + conda create -n my-env python=3.6 + +The term ``my-env`` can be anything. It names the new environment. + +Every time you open up a new Terminal / Command Prompt to work on your +project, activate that environment. This means that when you type ``python3`` +or, equivalently, ``python`` you will be getting a specific installation of +Python and Python packages, separate from any default installation on your +machine. + +.. code-block:: bash + + conda activate my-env + +The use of virtual environments leads to multiple instances of ``python``, +``pip``, ``ipython``, ``pytest``, ``flake8`` and other executables on your +machine. If you encounter unexpected behavior, use ``which ____`` to see which +environment a given command is coming from. (Linux and OSX only.) diff --git a/docs/source/example_pre-commit.yml b/docs/source/example_pre-commit.yml new file mode 100644 index 0000000..166940d --- /dev/null +++ b/docs/source/example_pre-commit.yml @@ -0,0 +1,14 @@ +repos: + - repo: https://github.com/ambv/black + rev: stable + hooks: + - id: black + language_version: python3.7 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.0.0 + hooks: + - id: flake8 + - repo: https://github.com/kynan/nbstripout + rev: 0.3.9 + hooks: + - id: nbstripout diff --git a/docs/source/example_travis.yml b/docs/source/example_travis.yml new file mode 100644 index 0000000..1e840e5 --- /dev/null +++ b/docs/source/example_travis.yml @@ -0,0 +1,22 @@ +# .travis.yml + +language: python +python: + - 3.9 +cache: + directories: + - $HOME/.cache/pip + - $HOME/.ccache # https://github.com/travis-ci/travis-ci/issues/5853 + +install: + # Install this package and the packages listed in requirements.txt. + - pip install . + # Install extra requirements for running tests and building docs. + - pip install -r requirements-dev.txt + +script: + - coverage run -m pytest # Run the tests and check for test coverage. + - coverage report -m # Generate test coverage report. + - codecov # Upload the report to codecov. + - flake8 --max-line-length=115 # Enforce code style (but relax line length limit a bit). + - make -C docs html # Build the documentation. diff --git a/docs/source/example_travis_with_doctr.yml b/docs/source/example_travis_with_doctr.yml new file mode 100644 index 0000000..26ceee9 --- /dev/null +++ b/docs/source/example_travis_with_doctr.yml @@ -0,0 +1,30 @@ +# .travis.yml + +language: python +python: + - 3.6 +cache: + directories: + - $HOME/.cache/pip + - $HOME/.ccache # https://github.com/travis-ci/travis-ci/issues/5853 + +env: + global: + # Doctr deploy key for YOUR_GITHUB_USERNAME/YOUR_REPO_NAME + - secure: "" + +install: + # Install this package and the packages listed in requirements.txt. + - pip install . + # Install extra requirements for running tests and building docs. + - pip install -r requirements-dev.txt + +script: + - coverage run -m pytest # Run the tests and check for test coverage. + - coverage report -m # Generate test coverage report. + - codecov # Upload the report to codecov. + - flake8 --max-line-length=115 # Enforce code style (but relax line length limit a bit). + - set -e # If any of the following steps fail, just stop at that point. + - make -C docs html # Build the documentation. + - pip install doctr + - doctr deploy --built-docs docs/build/html . # Publish the documentation. diff --git a/docs/source/further-reading.rst b/docs/source/further-reading.rst new file mode 100644 index 0000000..123f83f --- /dev/null +++ b/docs/source/further-reading.rst @@ -0,0 +1,22 @@ +============== +Futher Reading +============== + +Other Tutorials and Articles +---------------------------- + +* `Official Python Packaging Guide `_ +* `"SettingUpOpenSource" lesson plans by John Leeman `_ +* `"Structuring your Project" (written from the perspective of Python for web applications, as opposed to scientific libraries) `_ + +Other Python Cookiecutters +-------------------------- + +* https://github.com/tylerdave/cookiecutter-python-package +* https://github.com/audreyr/cookiecutter-pypackage + +Basics +------ + +* `Python Modules `_ +* `Software Carpentry Git Tutorial (For Novices) `_ diff --git a/docs/source/guiding-design-principles.rst b/docs/source/guiding-design-principles.rst new file mode 100644 index 0000000..07a690a --- /dev/null +++ b/docs/source/guiding-design-principles.rst @@ -0,0 +1,234 @@ +========================= +Guiding Design Principles +========================= + +In this section we summarize some guiding principles for designing and +organizing scientific Python code. + +Collaborate +----------- + +Software developed by several people is preferable to software developed by +one. By adopting the conventions and tooling used by many other scientific +software projects, you are well on your way to making it easy for others to +contribute. Familiarity works in both directions: it will be easier for others +to understand and contribute to your project, and it will be easier for you to +use other popular open-source scientific software projects and modify them to +your purposes. + +Talking through a design and the assumptions in it helps to clarify your +thinking. + +Collaboration takes trust. It is OK to be "wrong"; it is part of the process +of making things better. + +Having more than one person understanding every part of the code prevents +systematic risks for the project and keeps you from being tied to that code. + +If you can bring together contributors with diverse scientific backgrounds, it +becomes easier to identify functionality that should be generalized for reuse +by different fields. + +.. _refactor: + +Don't Be Afraid to Refactor +--------------------------- + +No code is ever right the first (or second) time. + +Refactoring the code once you understand the problem and the design trade-offs +more fully helps keep the code maintainable. Version control, tests, and +linting are your safety net, empowering you to make changes with confidence. + +Prefer "Wide" over "Deep" +------------------------- + +It should be possible to reuse pieces of software in a way not anticipated by +the original author. That is, branching out from the initial use case should +enable unplanned functionality without a massive increase in complexity. + +When building new things, work your way down to the lowest level, understand +that level, and then build back up. Try to imagine what else you would want to +do with the capability you are implementing for other research groups, for +related scientific applications, and next year. + +Take the time to understand how things need to work at the bottom. It is better +to slowly deploy a robust extensible solution than to quickly deploy a brittle +narrow solution. + +Keep I/O Separate +----------------- + +One of the biggest impediments to reuse of scientific code is when I/O +code---assuming certain file locations, names, formats, or layouts---is +interspersed with scientific logic. + +I/O-related functions should *only* perform I/O. For example, they should take +in a filepath and return a numpy array, or a dictionary of arrays and metadata. +The valuable scientific logic should be encoded in functions that take in +standard data types and return standard data types. This makes them easier to +test, maintain when data formats change, or reuse for unforeseen applications. + +Duck Typing is a Good Idea +-------------------------- + +`Duck typing `_ treats objects based +on what they can *do*, not based on what type they *are*. "If it walks like a +duck and it quacks like a duck, then it must be a duck." + +Python in general and scientific Python in particular leverage *interfaces* to +support interoperability and reuse. For example, it is possible to pass a +pandas DataFrame to the :func:`numpy.sum` function even though pandas was +created long after :func:`numpy.sum`. This is because :func:`numpy.sum` avoids +assuming it will be passed specific data types; it accepts any object that +provides the right methods (interfaces). Where possible, avoid ``isinstance`` +checks in your code, and try to make your functions work on the broadest +possible range of input types. + +"Stop Writing Classes" +---------------------- + +Not everything needs to be object-oriented. Object-oriented design frequently +does not add value in scientific computing. + +.. epigraph:: + + It is better to have 100 functions operate on one data structure than 10 + functions on 10 data structures. + + -- From ACM's SIGPLAN publication, (September, 1982), Article "Epigrams in + Programming", by Alan J. Perlis of Yale University. + +It is often tempting to invent special objects for a use case or workflow --- +an ``Image`` object or a ``DiffractionAnalysis`` object. This approach has +proven again and again to be difficult to extend and maintain. It is better to +prefer standard, simple data structures like Python dictionaries and numpy +arrays and use simple functions to operate on them. + +A popular talk, "Stop Writing Classes," which you can +`watch on YouTube `_, +illustrates how some situations that *seem* to lend themselves to +object-oriented programming are much more simply handled using plain, built-in +data structures and functions. + +As another example, the widely-used scikit-image library initially experimented +with using an ``Image`` class, but ultimately decided that it was better to use +plain old numpy arrays. All scientific Python libraries understand numpy +arrays, but they don't understand custom classes, so it is better to pass +application-specific metadata *alongside* a standard array than to try to +encapsulate all of that information in a new, bespoke object. + +Permissiveness Isn't Always Convenient +-------------------------------------- + +Overly permissive code can lead to very confusing bugs. If you need a flexible +user-facing interface that tries to "do the right thing" by guessing what the +users wants, separate it into two layers: a thin "friendly" layer on top of a +"cranky" layer that takes in only exactly what it needs and does the actual +work. The cranky layer should be easy to test; it should be constrained about +what it accepts and what it returns. This layered design makes it possible to +write *many* friendly layers with different opinions and different defaults. + +When it doubt, make function arguments required. Optional arguments are harder +to discover and can hide important choices that the user should know that they +are making. + +Exceptions should just be raised: don't catch them and print. Exceptions are a +tool for being clear about what the code needs and letting the caller decide +what to do about it. *Application* code (e.g. GUIs) should catch and handle +errors to avoid crashing, but *library* code should generally raise errors +unless it is sure how the user or the caller wants to handle them. + +Write Useful Error Messages +--------------------------- + +Be specific. Include what the wrong value was, what was wrong with it, and +perhaps how it might be fixed. For example, if the code fails to locate a file +it needs, it should say what it was looking for and where it looked. + +Write for Readability +--------------------- + +Unless you are writing a script that you plan to delete tomorrow or next week, +your code will probably be read many more times than it is written. And today's +"temporary solution" often becomes tomorrow's critical code. Therefore, +optimize for clarity over brevity, using descriptive and consistent names. + +Complexity is Always Conserved +------------------------------ + +Complexity is always conserved and is strictly greater than the system the code +is modeling. Attempts to hide complexity from the user frequently backfire. + +For example, it is often tempting to hide certain reused keywords in a +function, shortening this: + +.. code-block:: python + + def get_image(filename, normalize=True, beginning=0, end=None): + ... + +into this: + +.. code-block:: python + + def get_image(filename, options={}): + ... + +Although the interface appears to have been simplified through hidden keyword +arguments, now the user needs to remember what the ``options`` are or dig +through documentation to better understand how to use them. + +Because new science occurs when old ideas are reapplied or extended in +unforeseen ways, scientific code should not bury its complexity or overly +optimize for a specific use case. It should expose what complexity there is +straightforwardly. + +.. note:: + + Even better, you should consider using "keyword-only" arguments, introduced + in Python 3, which require the user to pass an argument by keyword rather + than position. + + .. code-block:: python + + get_image(filename, *, normalize=True, beginning=0, end=None): + ... + + Every argument after the ``*`` is keyword-only. Therefore, the usage + ``get_image('thing.png', False)`` will not be allowed; the caller must + explicitly type ``get_image('thing.png', normalize=False)``. The latter is + easier to read, and it enables the author to insert additional parameters + without breaking backward compatibility. + +Similarly, it can be tempting to write one function that performs multiple +steps and has many options instead of multiple functions that do a single step +and have few options. The advantages of "many small functions" reveal +themselves in time: + +* Small functions are easier to explain and document because their behavior is + well-scoped. +* Small functions can be tested individually, and it is easy to see which paths + have and have not yet been tested. +* It is easier to compose a function with other functions and reuse it in an + unanticipated way if its behavior is well-defined and tightly scoped. This is + `the UNIX philosophy `_: + "Do one thing and do it well." +* The number of possible interactions between arguments goes up with the number + of arguments, which makes the function difficult to reason about and test. In + particular, arguments whose meaning depends on other arguments should be + avoided. + +Functions should return the same kind of thing no matter what their arguments, +particularly their optional arguments. Violating "return type stability" puts +a burden on the function's caller, which now must understand the internal +details of the function to know what type to expect for any given input. That +makes the function harder to document, test, and use. Python does not enforce +return type stability, but we should try for it anyway. If you have a function +that returns different types of things depending on its inputs, that is a sign +that it should be :ref:`refactored ` into multiple functions. + +Python is incredibly flexible. It accommodates many possible design choices. +By exercising some restraint and consistency with the scientific Python +ecosystem, Python can be used to build scientific tools that last and grow well +over time. diff --git a/docs/source/including-data-files.rst b/docs/source/including-data-files.rst new file mode 100644 index 0000000..72c0179 --- /dev/null +++ b/docs/source/including-data-files.rst @@ -0,0 +1,126 @@ +==================== +Including Data Files +==================== + +In this section you will: + +* Understand the importance of keeping large files out of your package. +* Learn some alternative approaches. +* Learn how to include small data files in your package. + +Consider Alternatives +--------------------- + +**Never include large binary files in your Python package or git repository.** +Once committed, the file lives in git history forever. Git will become +sluggish, because it is not designed to operate on large binary files, and your +package will become an annoyingly large download. + +Removing accidentally-committed files after the fact is *possible* but +destructive, so it's important to avoid committing large files in the first +place. + +Alternatives: + +* Can you generate the file using code instead? This is a good approach for + test data: generate the test data files as part of the test. Of course it's + important to test against *real* data from time to time, but for automated + tests, simulated data is just fine. If you don't understand your data well + enough to simulate it accurately, you don't know enough to write useful tests + against it. +* Can you write a Python function that fetches the data on demand from some + public URL? This is the approach used by projects such as scikit-learn that + need to download large datasets for their examples and tests. + +If you use one these alternatives, add the names of the generated or downloaded +files to the project's ``.gitignore`` file, which was provided by the +cookiecutter template. This helps protect you against accidentally committing +the file to git. + +If the file in question is a text file and not very large (< 100 kB) than it's +reasonable to just bundle it with the package. + +How to Package Data Files +------------------------- + +What's the problem we are solving here? If your Python program needs to access +a data file, the naïve solution is just to hard-code the path to that file. + +.. code-block:: python + + data_file = open('peak_spacings/LaB6.txt') + +But this is not a good solution because: + +* The data file won't be included in the distribution: users who ``pip + install`` your package will find it's missing! +* The path to the data file depends on the platform and on how the package is + installed. We need Python to handle those details for us. + +As an example, suppose we have text files with Bragg peak spacings of various +crystalline structures, and we want to use these files in our Python package. +Let's put them in a new directory named ``peak_spacings/``. + +.. code-block:: text + + # peak_spacings/LaB6.txt + + 4.15772 + 2.94676 + 2.40116 + +.. code-block:: text + + # peak_spacings/Si.txt + + 3.13556044 + 1.92013079 + 1.63749304 + 1.04518681 + +To access these files from the Python package, you need to edit the code in +three places: + +#. Include the data files' paths to ``setup.py`` to make them accessible from + the package. + + .. code-block:: python + + # setup.py (excerpt) + + package_data={ + 'YOUR_PACKAGE_NAME': [ + # When adding files here, remember to update MANIFEST.in as well, + # or else they will not be included in the distribution on PyPI! + 'peak_spacings/*.txt', + ] + }, + + We have used the wildcard ``*`` to capture *all* filenames that end in + ``.txt``. We could alternatively have listed the specific filenames. + +#. Add the data files' paths to ``MANIFEST.in`` to include them in the source + distribution. By default the distribution omits extraneous files that are + not ``.py`` files, so we need to specifically include them. + + .. code-block:: text + + # MANIFEST.in (excerpt) + + include peak_spacings/*.txt + +#. Finally, wherever we actually use the files in our scientific code, we can + access them like this. + + .. code-block:: python + + from pkg_resources import resource_filename + + + filename = resource_filename('peak_spacings/LaB6.txt') + + # `filename` is the specific path to this file in this installation. + # We can now, for example, read the file. + with open(filename) as f: + # Read in each line and convert the string to a number. + spacings = [float(line) for line in f.read().splitlines()] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..a1eade1 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,50 @@ +.. Packaging Scientific Python documentation master file, created by + sphinx-quickstart on Thu Jun 28 12:35:56 2018. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Bootstrap a Scientific Python Library +===================================== + +This is a tutorial with a template for packaging, testing, documenting, and +publishing scientific Python code. + +Do you have a folder of disorganized scientific Python scripts? Are you always +hunting for code snippets scattered across dozens of Jupyter notebooks? Has it +become unwieldy to manage, update, and share with collaborators? This tutorial +is for you. + +See this lightning talk from Scipy 2018 for a short overview of the motivation +for and scope of this tutorial. + +.. youtube:: 1HDq7QoOlI4?start=1961 + +Starting from a working, full-featured template, you will: + +* Move your code into a version-controlled, installable Python package. +* Share your code on GitHub. +* Generate documentation including interactive usage examples and plots. +* Add automated tests to help ensure that new changes don't break existing + functionality. +* Use a free CI (continuous integration) service to automatically run your + tests against any proposed changes and automatically publish the latest + documentation when a change is made. +* Publish a release on PyPI so that users and collaborators can install your + code with pip. + +.. toctree:: + :maxdepth: 2 + + philosophy + preliminaries + the-code-itself + guiding-design-principles + ci + pre-commit + writing-docs + including-data-files + publishing-docs + publishing-releases + advanced-testing + environments + further-reading diff --git a/docs/source/philosophy.rst b/docs/source/philosophy.rst new file mode 100644 index 0000000..3cd3c6e --- /dev/null +++ b/docs/source/philosophy.rst @@ -0,0 +1,72 @@ +=========================== +Philosophy of this Tutorial +=========================== + +We aim to nudge scientist--developers toward good practices and standard tools, +to help small- and medium-sized projects start off on the right foot. Keeping +in mind that too much development infrastructure can be overwhelming to those +who haven't yet encountered the need for it, we stop short of recommending the +*full* stack of tools used by large scientific Python projects. + +This is an opinionated tutorial. We guide readers toward certain tools and +conventions. We do not always mention alternatives and their respective +trade-offs because we want to avoid overwhelming beginners with too many +decisions. Our choices are based on the current consensus in the scientific +Python community and the personal experience of the authors. + +The Tools, and the Problems They Solve +-------------------------------------- + +.. + Note: The bolded items here are intentionally not links. These things are + easy enough to Google if people want to know more, and I'd like to avoid + scaring anyone off by making them feel like they need to review the project + home pages for each of these projects before proceeding. + +* **Python 3** has been around since 2008, and it gained real traction in the + scientific Python ecosystem around 2014. It offers + `many benefits for scientific applications `_. + Python 2 will reach its end-of-life (no new security updates) in 2020. Some + projects still support both 2 and 3, but many have already dropped support + for Python 2 in new releases or + `publicly stated their plans to do so `_. + This cookiecutter focuses on Python 3 only. +* **Pip** is the official Python package manager, and it can install packages + either from code on your local machine or by downloading them from the Python + Package Index (PyPI). +* **Git** is a version control system that has become the consensus choice. It + is used by virtually all of the major scientific Python projects, including + Python itself. +* **GitHub** is a website, owned by Microsoft, for hosting git repositories and + discussion between contributors and users. +* **Cookiecutter** is a tool for generating a directory of files from a + template. We will use it to generate the boilerplate scaffolding of a Python + project and various configuration files without necessarily understanding all + the details up front. +* **PyTest** is a framework for writing code that tests other Python code. + There are several such frameworks, but pytest has emerged as the favorite, + and major projects like numpy have switched from older systems to pytest. +* **Flake8** is a tool for inspecting code for likely mistakes (such as a + variable that is defined but never used) and/or inconsitent style. This tool + is right "on the line" as far as what we would recommend for beginners. Your + mileage may vary, and so the tutorial includes clear instructions for + ommitting this piece. +* **Travis-CI** is an online service, free for open-source projects, that + speeds software development by checking out your code on a fresh, clean + server, installing your software, running the tests, and reporting the + results. This helps you ensure that your code will work on your colleague’s + computer---that it doesn’t accidentally depend on some local detail of your + machine. It also creates a clear, public record of whether the tests passed + or failed, so if things are accidentally broken (say, while you are on + vacation) you can trace when the breaking change occurred. +* **RestructuredText** (``.rst``) is a markup language, like HTML. It is + designed for writing software documentation. It is used by almost all + scientific Python projects, large and small. +* **Sphinx** is a documentation-publishing tool that renders + RestructuredText into HTML, PDF, LaTeX, and other useful formats. It also + inspects Python code to extract information (e.g. the list of functions in a + module and the arguments they expect) from which it can automatically + generate documentation. Extensions for sphinx provide additional + functionality, some of which we cover in this tutorial. +* **GitHub Pages** is a free service for publishing static websites. It is + suitable for publishing the documentation generated by sphinx. diff --git a/docs/source/pre-commit.rst b/docs/source/pre-commit.rst new file mode 100644 index 0000000..ea9d462 --- /dev/null +++ b/docs/source/pre-commit.rst @@ -0,0 +1,94 @@ +======================== +Git hooks and pre-commit +======================== + +In this section you will: + +* Learn about git hooks +* Configure pre-commit, a handy program to set up your git hooks. + +What are git hooks for? +----------------------- +`Git hooks `_ are way of running custom +scripts on your repository either client-side or server-side. In fact, server-side hooks can +be used to trigger things like continuous integration. Here we will focus on some client-side +(i.e. on your local machine) hooks that will ensure certain formatting standards +are met prior to commits. This way, all of your commits are meaningful changes to code, and +your git history doesn't get littered with "apply black" and "fix PEP-8" messages. +Notably these kinds of tests can also be run during +continuous integration (as seen in the configuration of :doc:`Travis-CI`). + +We also include a hook that scrubs any output from jupyter notebooks. +This way, the only time git thinks a notebook has changed is when the contents of the cells have changed. + +It's just formatting, who cares? +-------------------------------- +Ideally, someone else is going to read your code, and better yet, make changes to it. +Having a consistent code style and format makes that easier. Going a step further, +using an even more specific formatter such as `Black `_, +ensures that changes to programs produce the smallest changes to text possible. + +Configuring pre-commit +---------------------- +The cookiecutter template has already generated a configuration file for pre-commit, +which will construct your git hooks. + +.. literalinclude:: example_pre-commit.yml + +You can customize this to your liking, or not use it entirely. +For example, if you are migrating a large amount of existing code that is not compliant with +PEP8, yet managing notebooks collaboratively, +you may want to remove the section that does ``flake8`` style-checking and ``black``. + +Running ``pre-commit install`` inside the top-level directory of your repository +will use this configuration file to set up git hooks to run prior to completing a commit. + +Hooks included in this cookiecutter +----------------------------------- +- ``flake8``: Python style enforcement +- ``black``: An uncompromising formatter complient with flake8 +- ``isort``: An import sorter for added consistency +- Select hooks from ``pre-commit-hooks``: Whitespace enfrocement and yaml style-checking +- ``nbstripout``: Jupyter notebook stripping of output + +Committing Changes +------------------ +Assuming you've decided to keep ``nbstripout``, ``black``, and ``flake8`` as hooks, each +time you commit changes to the repository files will be checked for these standards. +If your files don't fit the standard, the commit will fail. + +For example with notebooks and ``nbstripout``: +**Before you commit changes to git** the *output* area of the notebooks must +be cleared. This ensures that (1) the potentially-large output artifacts +(such as figures) do not bloat the repository and (2) users visiting the +tutorial will see a clean notebook, uncluttered by any previous code +execution. This can be accomplished by running ``nbstripout`` before the commit. + +If you forget to do this, an error message will protect you from accidentally +committing. It looks like this:: + + $ git add . + $ git commit -m "oops" + nbstripout...............................................................Failed + - hook id: nbstripout + - files were modified by this hook + + +What happened here? Your attempt to commit has been blocked. The files have +been fixed for you---clearing the outputs from your notebooks, but +git-hooks won't assume you want these fixes committed. +Before trying again to commit, you must add those fixes to the "staged" changes:: + + # Stage again to include the fixes that we just applied (the cleared output areas). + $ git add . + + # Now try committing again. + $ git commit -m "this will work" + nbstripout...............................................................Passed + [main 315536e] changed things + 2 files changed, 44 insertions(+), 18 deletions(-) + + +The same procedure holds for applying black to files. +**However, Flake8 is a checker and not a formatter. +It will only report issues, not change them.** diff --git a/docs/source/preliminaries.rst b/docs/source/preliminaries.rst new file mode 100644 index 0000000..448ef34 --- /dev/null +++ b/docs/source/preliminaries.rst @@ -0,0 +1,314 @@ +=============== +Getting Started +=============== + +In this section you will: + +* Sign up for Github. +* Generate a scaffold for your new Python project. +* Upload it to GitHub. +* Install your new Python project for development. + +Then, in the next section, we will begin to move your scientific code into that +template. + +You will need a Terminal (Windows calls it a "Command Prompt") and a plain text +editor. Any will do; we won't assume anything about the editor you are using. +If you are looking for a recommendation for beginners, the `Atom Editor +`_ by GitHub is a good one. For minimalists, ``nano`` on +Linux or OSX and Notebook on Windows will get you up and running. + +Reading the steps that follow, you might reasonably wonder, "Why isn't there +just an automated script for this?" We prefer to do this process manually so +that we are forced to think carefully about each step, notice when something +goes wrong, and debug if necessary. We recommend you do the same. + +#. `Sign up for GitHub `_. + +#. Verify that you have Python 3. + + .. code-block:: bash + + python3 --version + + If necessary, install it by your method of choice: apt, Homebrew, conda, + etc. + +#. Create an *environment*, a sandboxed area for installing software that is + separate from the system defaults. This is not essential, but it is + strongly encouraged. It ensures that your project and its software + dependencies will not interfere with other Python software on your system. + There are several tools for this. But the simplest is Python's built-in + ``venv`` (short for "virtual environments"), illustrated here. + + Do this once: + + .. code-block:: bash + + python3 -m venv my-env + + The term ``my-env`` can be anything. It names the new environment. + + Do this every time you open up a new Terminal / Command Prompt to work on + your project: + + .. code-block:: bash + + . my-env/bin/activate + + .. note:: + + If you are a conda user, you may prefer a conda environment: + + .. code-block:: bash + + conda create -n my-env python=3.7 + conda activate my-env + +#. Verify that you have git installed. + + .. code-block:: bash + + git + + If necessary, install it by your method of choice (apt, Homebrew, conda, etc.). + +#. Choose a name for your project. + + Ideal names are descriptive, succinct, and easy to Google. Think about who + else might be interested in using or contributing to your project in the + future, and choose a name that will help that person discover your project. + There is no need to put "py" in the name; usually your user will already + know that this is a Python project. + + Check that the name is not already taken by + `searching for it on the Python Package Index (PyPI) `_. + +#. Install cookiecutter. + + .. code-block:: bash + + python3 -m pip install --upgrade cookiecutter + +#. Generate a new Python project using our cookiecutter template. + + .. code-block:: bash + + cookiecutter https://github.com/NSLS-II/scientific-python-cookiecutter + + + You will see the following the prompts. The default suggestion is given in square brackets. + + For the last question, ``minimum_supported_python_version``, we recommend + supporting back to Python 3.6 unless you have a need for newer Python + features. + + .. code-block:: bash + + full_name [Name or Organization]: Brookhaven National Lab + email []: dallan@bnl.gov + github_username []: danielballan + project_name [Your Project Name]: Example + package_dist_name [example]: + package_dir_name [example]: + repo_name [example]: + project_short_description [Python package for doing science.]: Example package for docs. + year [2018]: + Select minimum_supported_python_version: + 1 - Python 3.6 + 2 - Python 3.7 + 3 - Python 3.8 + Choose from 1, 2, 3 [1]: + + This generates a new directory, ``example`` in this case, with all the + "scaffolding" of a working Python project. + + .. code-block:: bash + + $ ls example/ + AUTHORS.rst MANIFEST.in example setup.cfg + CONTRIBUTING.rst README.rst requirements-dev.txt setup.py + LICENSE docs requirements.txt versioneer.py + + .. note:: + + Cookiecutter prompted us for several variations of *name*. + If are you wondering what differentiates all these names, here's a primer: + + * ``project_name`` -- Human-friendly title. Case sensitive. Spaces allowed. + * ``package_dist_name`` -- The name to use when you ``pip install ___``. + Dashes and underscores are allowed. Dashes are conventional. Case + insensitive. + * ``package_dir_name`` --- The name to use when you ``import ___`` in Python. + Underscores are the only punctuation allowed. Conventionally lowercase. + * ``repo_name`` --- The name of the GitHub repository. This will be the + name of the new directory on your filesystem. + +#. Take a moment to see what we have. (Some systems treat files whose name + begins with ``.`` as "hidden files", not shown by default. Use the ``ls -a`` + command in the Terminal to show them.) + + .. The following code-block output was generated using `tree -a example/`. + + .. code-block:: none + + example/ + ├── .flake8 + ├── .gitattributes + ├── .gitignore + ├── .travis.yml + ├── AUTHORS.rst + ├── CONTRIBUTING.rst + ├── LICENSE + ├── MANIFEST.in + ├── README.rst + ├── docs + │   ├── Makefile + │   ├── build + │   ├── make.bat + │   └── source + │   ├── _static + │   │   └── .placeholder + │   ├── _templates + │   ├── conf.py + │   ├── index.rst + │   ├── installation.rst + │   ├── release-history.rst + │   └── usage.rst + ├── example + │   ├── __init__.py + │   ├── _version.py + │   └── tests + │   └── test_examples.py + ├── requirements-dev.txt + ├── requirements.txt + ├── setup.cfg + ├── setup.py + └── versioneer.py + + In this top ``example/`` directory, we have files specifying metadata about + the Python package (e.g. ``LICENSE``) and configuration files related to + tools we will cover in later sections. We are mostly concerned with the + ``example/example/`` subdirectory, which is the Python package itself. This + is where we'll put the scientific code. But first, we should version-control + our project using git. + +#. Change directories into your new project. + + .. code-block:: bash + + cd example + + We are now in the top-level ``example/`` directory---not ``example/example``! + +#. Make the directory a git repository. + + .. code-block:: bash + + $ git init + Initialized empty Git repository in (...) + +#. Make the first "commit". If we break anything in later steps, we can always + roll back to this clean initial state. + + .. code-block:: bash + + $ git add . + $ git commit -m "Initial commit." + +#. `Create a new repository on GitHub `_, + naming it with the ``repo_name`` from your cookiecutter input above. + + .. important:: + + Do **not** check "Initialize this repository with a README". + +#. Configure your local repository to know about the remote repository on + GitHub... + + .. code-block:: bash + + $ git remote add origin https://github.com/YOUR_GITHUB_USER_NAME/YOUR_REPOSITORY_NAME. + + ... and upload the code. + + .. code-block:: bash + + $ git push -u origin master + Counting objects: 42, done. + Delta compression using up to 4 threads. + Compressing objects: 100% (40/40), done. + Writing objects: 100% (42/42), 29.63 KiB | 0 bytes/s, done. + Total 42 (delta 4), reused 0 (delta 0) + remote: Resolving deltas: 100% (4/4), done. + To github.com:YOUR_GITHUB_USER_NAME/YOUR_REPO_NAME.git + * [new branch] master -> master + Branch master set up to track remote branch master from origin. + + + .. note:: + + If this repository is to belong to a GitHub *organization* (e.g. + http://github.com/NSLS-II) as opposed to a personal user account + (e.g. http://github.com/danielballan) it is conventional to name the + organization remote ``upstream`` instead of ``origin``. + + .. code-block:: bash + + $ git remote add upstream https://github.com/ORGANIZATION_NAME/YOUR_REPOSITORY_NAME. + $ git push -u upstream master + Counting objects: 42, done. + Delta compression using up to 4 threads. + Compressing objects: 100% (40/40), done. + Writing objects: 100% (42/42), 29.63 KiB | 0 bytes/s, done. + Total 42 (delta 4), reused 0 (delta 0) + remote: Resolving deltas: 100% (4/4), done. + To github.com:ORGANIZATION_NAME/YOUR_REPO_NAME.git + * [new branch] master -> master + Branch master set up to track remote branch master from upstream. + + and, separately, add your personal fork as ``origin``. + + .. code-block:: bash + + $ git remote add origin https://github.com/YOUR_GITHUB_USER_NAME/YOUR_REPOSITORY_NAME. + +#. Now let's install your project for development. + + .. code-block:: python + + python3 -m pip install -e . + + .. note:: + + The ``-e`` stands for "editable". It uses simlinks to link to the actual + files in your repository (rather than copying them, which is what plain + ``pip install .`` would do) so that you do not need to re-install the + package for an edit to take effect. + + This is similar to the behavior of ``python setup.py develop``. If you + have seen that before, we recommend always using ``pip install -e .`` + instead because it avoids certain pitfalls. + +#. Finally, verify that we can import it. + + .. code-block:: bash + + python3 + + .. code-block:: python + + >>> import your_package_name + +#. Looking ahead, we'll also need the "development requirements" for our + package. These are third-party Python packages that aren't necessary to + *use* our package, but are necessary to *develop* it (run tests, build the + documentation). The cookiecutter template has listed some defaults in + ``requirements-dev.txt``. Install them now. + + .. code-block:: bash + + python3 -m pip install --upgrade -r requirements-dev.txt + +Now we have a working but empty Python project. In the next section, we'll +start moving your scientific code into the project. diff --git a/docs/source/publish-docs.yml b/docs/source/publish-docs.yml new file mode 100644 index 0000000..d5860ea --- /dev/null +++ b/docs/source/publish-docs.yml @@ -0,0 +1,60 @@ +name: Publish Documentation + +on: + push: + branches: + - main + +jobs: + build: + # This conditional ensures that forks on GitHub do not attempt to publish + # documentation, only the "upstream" one. (If forks _did_ try to publish + # they would fail later below because they would not have access to the + # secret credentials. But it's better to opt out here.) + if: github.repository_owner == 'YOUR_GITHUB_USERNAME_OR_ORGANIZATION' + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.8] + fail-fast: false + + steps: + + - name: Set env.REPOSITORY_NAME # just the repo, as opposed to org/repo + shell: bash -l {0} + run: | + export REPOSITORY_NAME=${GITHUB_REPOSITORY#*/} + echo "REPOSITORY_NAME=${REPOSITORY_NAME}" >> $GITHUB_ENV + + - uses: actions/checkout@v2 + with: + fetch-depth: 1000 # should be enough to reach the most recent tag + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install + shell: bash -l {0} + run: source continuous_integration/scripts/install.sh + + - name: Install documentation-building requirements + shell: bash -l {0} + run: | + set -vxeuo pipefail + python -m pip install -r requirements-dev.txt + python -m pip list + + - name: Build Docs + shell: bash -l {0} + run: make -C docs/ html + + - name: Deploy documentation to blueskyproject.io. + # We pin to the SHA, not the tag, for security reasons. + # https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions + uses: peaceiris/actions-gh-pages@bbdfb200618d235585ad98e965f4aafc39b4c501 # v3.7.3 + with: + deploy_key: ${{ secrets.ACTIONS_DOCUMENTATION_DEPLOY_KEY }} + publish_branch: gh-pages + publish_dir: ./docs/build/html diff --git a/docs/source/publishing-docs.rst b/docs/source/publishing-docs.rst new file mode 100644 index 0000000..02f9f94 --- /dev/null +++ b/docs/source/publishing-docs.rst @@ -0,0 +1,121 @@ +============================ +Publishing the Documentation +============================ + +In this section you will publish your documentation on a public website hosted +by GitHub Pages. + +This section assume you have already set up Travis-CI, as we covered in a +previous section, :doc:`ci`. + +Configure Doctr +--------------- + +We recommend `doctr `_ a tool that configures +Travis-CI to automatically publish your documentation every time the master +branch is updated. + +.. warning:: + The repo you want to build the docs for has to be a root repo. + You cannot build docs for a forked repo by doctr yet. + The doctr team is working on enabling a forked repo build under + `PR #343 `_. + +Install doctr. + + .. code-block:: bash + + python3 -m pip install --upgrade doctr + +Just type ``doctr configure`` and follow the prompts. Example: + +.. code-block:: bash + + $ doctr configure + Welcome to Doctr. + + We need to ask you a few questions to get you on your way to automatically + deploying from Travis CI to GitHub pages. + + What is your GitHub username? YOUR_GITHUB_USERNAME + Enter the GitHub password for YOUR_GITHUB_USERNAME: + A two-factor authentication code is required: app + Authentication code: XXXXXX + What repo do you want to build the docs for (org/reponame, like 'drdoctr/doctr')? YOUR_GITHUB_USERNAME/YOUR_REPO_NAME + What repo do you want to deploy the docs to? [YOUR_GITHUB_USERNAME/YOUR_REPO_NAME] + + The deploy key has been added for YOUR_GITHUB_USERNAME/YOUR_REPO_NAME. + + You can go to https://github.com/NSLS-II/NSLS-II.github.io/settings/keys to revoke the deploy key. + +Then doctr shows some instructions, which we will take one at a time, +elaborating here. + +.. code-block:: bash + + ================== You should now do the following ================== + + 1. Add the file github_deploy_key_your_github_username_your_repo_name.enc to be staged for commit: + + git add github_deploy_key_your_github_username_your_repo_name.enc + +Remember that you can always use ``git status`` to list uncommitted files like +this one. + +.. code-block:: bash + + 2. Add these lines to your `.travis.yml` file: + + env: + global: + # Doctr deploy key for YOUR_GITHUB_USERNAME/YOUR_REPO_NAME + - secure: "" + + script: + - set -e + - + - pip install doctr + - doctr deploy --built-docs + + Replace the text in with the relevant + things for your repository. + + Note: the `set -e` prevents doctr from running when the docs build fails. + We put this code under `script:` so that if doctr fails it causes the + build to fail. + +This output includes an encrypted token --- the string next to ``secure:`` +which I have redacted in the example above --- that gives Travis-CI permission +to upload to GitHub Pages. + +Putting this together with the default ``.travis.yml`` file generated by the +cookiecutter template, you'll have something like: + +.. literalinclude:: example_travis_with_doctr.yml + :emphasize-lines: 11-14,27-30 + +where ```` is replaced by the output you got from ``doctr configure`` +above. + +.. code-block:: bash + + 3. Commit and push these changes to your GitHub repository. + The docs should now build automatically on Travis. + + See the documentation at https://drdoctr.github.io/ for more information. + +Once the changes are pushed to the ``master`` branch on GitHub, Travis-CI will +publish the documentation to +``https://.github.io/``. It may take a +minute for the updates to process. + +Alternatives +------------ + +Another popular option for publishing documentation is +`https://readthedocs.org/ `_ . We slightly prefer +using Travis-CI + GitHub Pages because it is easier to debug any installation +issues. It is also more efficient: we have to build the documentation on +Travis-CI anyway to verify that any changes haven't broken them, so we might as +well upload the result and be done, rather than having readthedocs build them +again. diff --git a/docs/source/publishing-releases.rst b/docs/source/publishing-releases.rst new file mode 100644 index 0000000..762b9d9 --- /dev/null +++ b/docs/source/publishing-releases.rst @@ -0,0 +1,157 @@ +=================== +Publishing Releases +=================== + +In this section you will: + +* Tag a release of your Python package. +* Upload it to `PyPI `_ (Python Package Index) so that + users can download and install it using pip. + +We strongly encourage you to share your code GitHub from the start, which is +why we covered it in :doc:`preliminaries`. People often overestimate the risks +and underestimate the benefits of making their research code public, and the +idea of waiting to make it public "until it's cleaned up" is a punchline, an +exercise in infinite regress. But *releases* are little different: you should +wait to publish a release until your package is usable and tested. + +#. Choose a version number. The convention followed by most scientific Python + packages is ``vMAJOR.MINOR.MICRO``, as in ``v1.3.0``. A good number to start + with is ``v0.1.0``. + + These numbers have meanings. + The goal is to communicate to the user whether upgrading will break anything + in *their* code that will need to be updated in turn. This is + `semantic versioning `_. + + * Incrementing the ``MICRO`` number (``v1.3.0`` -> ``v1.3.1``) means, "I + have fixed some bugs, but I have not added any major new features or + changed any names. All of your code should still work without changes." + * Incrementing the ``MINOR`` number (``v1.3.0`` -> ``v1.4.0``) means, "I + have added some new features and changed some minor things. Your code + should work with perhaps some small updates." + * Incrementing the ``MAJOR`` number (``v1.3.0`` -> ``v2.0.0``) means, "I + have made major changes that will probably require you to update your + code." + + Additionally, if the ``MAJOR`` version is ``0``, the project is considered + to be in early development and may make major breaking changes in minor + releases. + + Obviously this is an imprecise system. Think of it a highly-compressed, + lossy representation of how painful it will be for the user to upgrade. + +#. Update ``docs/source/release-history.rst`` in the documentation if you have + not done so already. (See :doc:`writing-docs`.) For the first tagged + release, you don't need to write much --- some projects just write "Initial + release" under the heading with the version and release date. But for every + subsequent release, you should list any alterations that could require users + of your Python package to change their code. You may also highlight any + additions, improvements, and bug fixes. As examples, see + `the release notes for this small project `_ + and + `this large project `_. + +#. Type ``git status`` and check that you are on the ``master`` branch with no + uncommitted code. + +#. Mark the release with an empty commit, just to leave a marker. This is + optional, but it makes it easier to find the release when skimming through + the git history. + + .. code-block:: bash + + git commit --allow-empty -m "REL: vX.Y.Z" + +#. Tag the commit. + + .. code-block:: bash + + git tag -a vX.Y.Z # Don't forget the leading v + + This will create a tag named ``vX.Y.Z``. The ``-a`` flag (strongly + recommended) opens up a text editor where you should enter a brief + description of the release, such as "This releases fixes some bugs but does + not introduce any breaking changes. All users are encouraged to upgrade." + +#. Verify that the ``__version__`` attribute is correctly updated. + + The version is reported in three places: + + 1. The git tag + 2. The ``setup(version=...)`` parameter in the ``setup.py`` file + 3. Your package's ``__version__`` attribute, in Python + + `Versioneer `_, which was + included and configured for you by the cookiecutter template, automatically + keeps these three in sync. Just to be sure that it worked properly, start up + Python, import the module, and check the ``__version__``. It should have + automatically updated to match the tag. The leading ``v`` is not included. + + .. code-block:: python + + import your_package + your_package.__version__ # should be 'X.Y.Z' + + Incidentally, once you resume development and add the first commit after + this tag, ``__version__`` will take on a value like ``X.Y.Z+1.g58ad5f7``, + where ``+1`` means "1 commit past version X.Y.Z" and ``58ad5f7`` is the + first 7 characters of the hash of the current commit. The letter ``g`` + stands for "git". This is all managed automatically by versioneer and in + accordance with the specification in + `PEP 440 `_. + +#. Push the new commit and the tag to ``master``. + + .. code-block:: bash + + git push origin master + git push origin vX.Y.Z + + .. note:: + + Check your remotes using ``git remote -v``. If your respoitory is + stored in an organization account, you may need to push to ``upstream`` + as well as ``origin``. + +#. `Register for a PyPI account `_. + +#. Install wheel, a tool for producing `built distributions `_ for PyPI. + + .. code-block:: bash + + python3 -m pip install --upgrade wheel + +#. Remove any extraneous files. If you happen to have any important files in + your project directory that are not committed to git, move them first; this + will delete them! + + .. code-block:: bash + + git clean -dfx + +#. Publish a release on PyPI. Note that you might need to configure + your ``~/.pypirc`` with a login token. See `the packaging documentation `_ for more details. + + .. code-block:: bash + + python3 setup.py sdist + python3 setup.py bdist_wheel + twine upload dist/* + +The package is now installable with pip. It may take a couple minutes to become +available. + +If you would also like to make your package available via conda, we recommend +conda-forge, a community-led collection of recipes and build infrastructure. +See in particular +`the section of the conda-forge documentation on adding a recipe `_. + +#. Finally, if you generally work with an "editable" installation of the + package on your machine, as we suggested in :doc:`preliminaries`, you'll + need to reinstall because running ``git clean -dfx`` above will have wiped + out your installation. + + .. code-block:: bash + + pip install -e . diff --git a/docs/source/python-publish.yml b/docs/source/python-publish.yml new file mode 100644 index 0000000..5c62576 --- /dev/null +++ b/docs/source/python-publish.yml @@ -0,0 +1,33 @@ +# This workflows will upload a Python Package using flit when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install wheel twine setuptools + - name: Build and publish + env: + TWINE_USERNAME: __token__ + # The PYPI_PASSWORD must be a pypi token with the "pypi-" prefix with sufficient permissions to upload this package + # https://pypi.org/help/#apitoken + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/docs/source/refraction.py b/docs/source/refraction.py new file mode 100644 index 0000000..2e2c4e9 --- /dev/null +++ b/docs/source/refraction.py @@ -0,0 +1,32 @@ +# example/refraction.py + +import numpy as np + + +def snell(theta_inc, n1, n2): + """ + Compute the refraction angle using Snell's Law. + + See https://en.wikipedia.org/wiki/Snell%27s_law + + Parameters + ---------- + theta_inc : float + Incident angle in radians. + n1, n2 : float + The refractive index of medium of origin and destination medium. + + Returns + ------- + theta : float + refraction angle + + Examples + -------- + A ray enters an air--water boundary at pi/4 radians (45 degrees). + Compute exit angle. + + >>> snell(np.pi/4, 1.00, 1.33) + 0.5605584137424605 + """ + return np.arcsin(n1 / n2 * np.sin(theta_inc)) diff --git a/docs/source/test_examples.py b/docs/source/test_examples.py new file mode 100644 index 0000000..7c92510 --- /dev/null +++ b/docs/source/test_examples.py @@ -0,0 +1,26 @@ +# example/tests/test_examples.py + +import numpy as np +from ..refraction import snell +# (The above is equivalent to `from example.refraction import snell`. +# Read on for why.) + + +def test_perpendicular(): + # For any indexes, a ray normal to the surface should not bend. + # We'll try a couple different combinations of indexes.... + + actual = snell(0, 2.00, 3.00) + expected = 0 + assert actual == expected + + actual = snell(0, 3.00, 2.00) + expected = 0 + assert actual == expected + + +def test_air_water(): + n_air, n_water = 1.00, 1.33 + actual = snell(np.pi/4, n_air, n_water) + expected = 0.5605584137424605 + assert np.allclose(actual, expected) diff --git a/docs/source/the-code-itself.rst b/docs/source/the-code-itself.rst new file mode 100644 index 0000000..b5f340d --- /dev/null +++ b/docs/source/the-code-itself.rst @@ -0,0 +1,374 @@ +=============== +The Code Itself +=============== + +In this section you will: + +* Put some scientific code in your new Python package. +* Update your package's list of dependencies in ``requirements.txt``. +* Write a test and run the test suite. +* Use a "linter" and style-checker. +* Commit your changes to git and sync your changes with GitHub. + +A simple function with inline documentation +------------------------------------------- + +Let's write a simple function that encodes +`Snell's Law `_ and include it in +our Python package. + +Look again at the directory structure. + +.. code-block:: none + + example/ + ├── .flake8 + ├── .gitattributes + ├── .gitignore + ├── .travis.yml + ├── AUTHORS.rst + ├── CONTRIBUTING.rst + ├── LICENSE + ├── MANIFEST.in + ├── README.rst + ├── docs + │   ├── Makefile + │   ├── build + │   ├── make.bat + │   └── source + │   ├── _static + │   │   └── .placeholder + │   ├── _templates + │   ├── conf.py + │   ├── index.rst + │   ├── installation.rst + │   ├── release-history.rst + │   └── usage.rst + ├── example + │   ├── __init__.py + │   ├── _version.py + │   └── tests + │   └── test_examples.py + ├── requirements-dev.txt + ├── requirements.txt + ├── setup.cfg + ├── setup.py + └── versioneer.py + +Our scientific code should go in the ``example/`` subdirectory, next to +``__init__.py``. Let's make a new file in that directory named +``refraction.py``, meaning our new layout will be: + +.. code-block:: none + + ├── example + │   ├── __init__.py + │   ├── _version.py + │   ├── refraction.py + │   └── tests + │   └── test_examples.py + +This is our new file. You may follow along exactly or, instead, make a file +with a different name and your own scientific function. + +.. literalinclude:: refraction.py + +Notice that this example includes inline documentation --- a "docstring". This +is extremely useful for collaborators, and the most common collaborator is +Future You! + +Further, by following the +`numpydoc standard `_, +we will be able to automatically generate nice-looking HTML documentation +later. Notable features: + +* There is a succinct, one-line summary of the function's purpose. It must one + line. +* (Optional) There is an paragraph elaborating on that summary. +* There is a section listing input parameters, with the structure + + .. code-block :: none + + parameter_name : parameter_type + optional description + + Note that space before the ``:``. That is part of the standard. +* Similar parameters may be combined into one entry for brevity's sake, as we + have done for ``n1, n2`` here. +* There is a section describing what the function returns. +* (Optional) There is a section of one or more examples. + +We will revisit docstrings in the section on :doc:`writing-docs`. + +Update Requirements +------------------- + +Notice that our package has a third-party dependency, numpy. We should +update our package's ``requirements.txt``. + +.. code-block:: text + + # requirements.txt + + # List required packages in this file, one per line. + numpy + +Our cookiecutter configured ``setup.py`` to read this file. It will ensure that +numpy is installed when our package is installed. + +We can test it by reinstalling the package. + +.. code-block:: bash + + python3 -m pip install -e . + +Try it +------ + +Try importing and using the function. + + +.. code-block:: python + + >>> from example.refraction import snell + >>> import numpy as np + >>> snell(np.pi/4, 1.00, 1.33) + 1.2239576240104186 + +The docstring can be viewed with :func:`help`. + +.. code-block:: python + + >>> help(snell) + +Or, as a shortcut, use ``?`` in IPython/Jupyter. + +.. ipython:: python + :verbatim: + + snell? + +Run the Tests +------------- + +You should add a test right away while the details are still fresh in mind. +Writing tests encourages you to write modular, reusable code, which is easier +to test. + +The cookiecutter template included an example test suite with one test: + +.. code-block:: python + + # example/tests/test_examples.py + + def test_one_plus_one_is_two(): + assert 1 + 1 == 2 + +Before writing our own test, let's practice running that test to check that +everything is working. + +.. important:: + + We assume you have installed the "development requirements," as covered + in :doc:`preliminaries`. If you are not sure whether you have, there is no + harm in running this a second time: + + .. code-block:: bash + + python3 -m pip install --upgrade -r requirements-dev.txt + +.. code-block:: bash + + python3 -m pytest + +This walks through all the directories and files in our package that start with +the word 'test' and collects all the functions whose name also starts with +``test``. Currently, there is just one, ``test_one_plus_one_is_two``. +``pytest`` runs that function. If no exceptions are raised, the test passes. + +The output should look something like this: + +.. code-block:: bash + + ======================================== test session starts ======================================== + platform darwin -- Python 3.6.4, pytest-3.6.2, py-1.5.4, pluggy-0.6.0 + benchmark: 3.1.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) + rootdir: /private/tmp/test11/example, inifile: + plugins: xdist-1.22.2, timeout-1.2.1, rerunfailures-4.0, pep8-1.0.6, lazy-fixture-0.3.0, forked-0.2, benchmark-3.1.1 + collected 1 item + + example/tests/test_examples.py . [100%] + + ===================================== 1 passed in 0.02 seconds ====================================== + +.. note:: + + The output of ``pytest`` is customizable. Commonly useful command-line + arguments include: + + * ``-v`` verbose + * ``-s`` Do not capture stdout/err per test. + * ``-k EXPRESSION`` Filter tests by pattern-matching test name. + + Consult the `pytest documentation `_ + for more. + +Write a Test +------------ + +Let's add a test to ``test_examples.py`` that exercises our ``snell`` function. +We can delete ``test_one_plus_one_is_two`` now. + +.. literalinclude:: test_examples.py + +Things to notice: + +* It is sometime useful to put multiple ``assert`` statements in one test. You + should make a separate test for each *behavior* that you are checking. When a + monolithic, multi-step tests fails, it's difficult to figure out why. +* When comparing floating-point numbers (as opposed to integers) you should not + test for exact equality. Use :func:`numpy.allclose`, which checks for + equality within a (configurable) tolerance. Numpy provides several + `testing utilities `_, + which should always be used when testing numpy arrays. +* Remember that the names of all test modules and functions must begin with + ``test`` or they will not be picked up by pytest! + +See :doc:`advanced-testing` for more. + +"Lint": Check for suspicious-looking code +----------------------------------------- + +A `linter `_ is a tool that +analyzes code to flag potential errors. For example, it can catch variables you +defined by never used, which is likely a spelling error. + +The cookiecutter configured ``flake8`` for this purpose. Flake8 checks for +"lint" and also enforces the standard Python coding style, +`PEP8 `_. Enforcing +consistent style helps projects stay easy to read and maintain as they grow. +While not all projects strictly enfore PEP8, we generally recommend it. + +.. important:: + + We assume you have installed the "development requirements," as covered + in :doc:`preliminaries`. If you are not sure whether you have, there is no + harm in running this a second time: + + .. code-block:: bash + + python3 -m pip install --upgrade -r requirements-dev.txt + +.. code-block:: bash + + python3 -m flake8 + +This will list linting or stylistic errors. If there is no output, all is well. +See the `flake8 documentation `_ for more. + +Commit and Push Changes +----------------------- + +Remember to commit your changes to version control and push them up to GitHub. + +.. important:: + + The following is a quick reference that makes some assumptions about your + local configuration and workflow. + + This usage is part of a workflow named *GitHub flow*. See + `this guide `_ for more. + +Remember that at any time you may use ``git status`` to check which branch +you are currently on and which files have uncommitted changes. Use ``git diff`` +to review the content of those changes. + +1. If you have not already done so, create a new "feature branch" for this work + with some descriptive name. + + .. code-block:: bash + + git checkout master # Starting from the master branch... + git checkout -b add-snell-function # ...make a new branch. + +2. Stage changes to be committed. In our example, we have created one new file + and changed an existing one. We ``git add`` both. + + .. code-block:: bash + + git add example/refraction.py + git add example/tests/test_examples.py + +3. Commit changes. + + .. code-block:: bash + + git commit -m "Add snell function and tests." + +4. Push changes to remote repository on GitHub. + + .. code-block:: bash + + git push origin add-snell-function + +5. Repeat steps 2-4 until you are happy with this feature. + +6. Create a Pull Request --- or merge to master. + + When you are ready for collaborators to review your work and consider merging + the ``add-snell-function`` branch into the ``master`` branch, + `create a pull request `_. + Even if you presently have no collaborators, going through this process is a + useful way to document the history of changes to the project for any *future* + collaborators (and Future You). + + However, if you are in the early stages of just getting a project up and you + are the only developer, you might skip the pull request step and merge the + changes yourself. + + .. code-block:: bash + + git checkout master + # Ensure local master branch is up to date with remote master branch. + git pull --ff-only origin master + # Merge the commits from add-snell-function into master. + git merge add-snell-function + # Update the remote master branch. + git push origin master + +Multiple modules +---------------- + +We created just one module, ``example.refraction``. We might eventually grow a +second module --- say, ``example.utils``. Some brief advice: + +* When in doubt, resist the temptation to grow deep taxonomies of modules and + sub-packages, lest it become difficult for users and collaborators to + remember where everything is. The Python built-in libraries are generally + flat. + +* When making intra-package imports, we recommend relative imports. + + This works: + + .. code-block:: bash + + # example/refraction.py + + from example import utils + from example.utils import some_function + + but this is equivalent, and preferred: + + .. code-block:: bash + + # example/refraction.py + + from . import utils + from .utils import some_function + + For one thing, if you change the name of the package in the future, you won't + need to update this file. + +* Take care to avoid circular imports, wherein two modules each import the + other. diff --git a/docs/source/vendor/youtube.py b/docs/source/vendor/youtube.py new file mode 100644 index 0000000..a42a47d --- /dev/null +++ b/docs/source/vendor/youtube.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# VENDORED FROM +# https://github.com/sphinx-contrib/youtube/blob/master/sphinxcontrib/youtube.py + +from __future__ import division + +import re +from docutils import nodes +from docutils.parsers.rst import directives, Directive + +CONTROL_HEIGHT = 30 + +def get_size(d, key): + if key not in d: + return None + m = re.match("(\d+)(|%|px)$", d[key]) + if not m: + raise ValueError("invalid size %r" % d[key]) + return int(m.group(1)), m.group(2) or "px" + +def css(d): + return "; ".join(sorted("%s: %s" % kv for kv in d.items())) + +class youtube(nodes.General, nodes.Element): pass + +def visit_youtube_node(self, node): + aspect = node["aspect"] + width = node["width"] + height = node["height"] + + if aspect is None: + aspect = 16, 9 + + if (height is None) and (width is not None) and (width[1] == "%"): + style = { + "padding-top": "%dpx" % CONTROL_HEIGHT, + "padding-bottom": "%f%%" % (width[0] * aspect[1] / aspect[0]), + "width": "%d%s" % width, + "position": "relative", + } + self.body.append(self.starttag(node, "div", style=css(style))) + style = { + "position": "absolute", + "top": "0", + "left": "0", + "width": "100%", + "height": "100%", + "border": "0", + } + attrs = { + "src": "https://www.youtube.com/embed/%s" % node["id"], + "style": css(style), + } + self.body.append(self.starttag(node, "iframe", **attrs)) + self.body.append("") + else: + if width is None: + if height is None: + width = 560, "px" + else: + width = height[0] * aspect[0] / aspect[1], "px" + if height is None: + height = width[0] * aspect[1] / aspect[0], "px" + style = { + "width": "%d%s" % width, + "height": "%d%s" % (height[0] + CONTROL_HEIGHT, height[1]), + "border": "0", + } + attrs = { + "src": "https://www.youtube.com/embed/%s" % node["id"], + "style": css(style), + } + self.body.append(self.starttag(node, "iframe", **attrs)) + self.body.append("") + +def depart_youtube_node(self, node): + pass + +def visit_youtube_node_latex(self,node): + self.body.append(r'\begin{quote}\begin{center}\fbox{\url{https://www.youtu.be/%s}}\end{center}\end{quote}'%node['id']) + + +class YouTube(Directive): + has_content = True + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = { + "width": directives.unchanged, + "height": directives.unchanged, + "aspect": directives.unchanged, + } + + def run(self): + if "aspect" in self.options: + aspect = self.options.get("aspect") + m = re.match("(\d+):(\d+)", aspect) + if m is None: + raise ValueError("invalid aspect ratio %r" % aspect) + aspect = tuple(int(x) for x in m.groups()) + else: + aspect = None + width = get_size(self.options, "width") + height = get_size(self.options, "height") + return [youtube(id=self.arguments[0], aspect=aspect, width=width, height=height)] + + +def unsupported_visit_youtube(self, node): + self.builder.warn('youtube: unsupported output format (node skipped)') + raise nodes.SkipNode + + +_NODE_VISITORS = { + 'html': (visit_youtube_node, depart_youtube_node), + 'latex': (visit_youtube_node_latex, depart_youtube_node), + 'man': (unsupported_visit_youtube, None), + 'texinfo': (unsupported_visit_youtube, None), + 'text': (unsupported_visit_youtube, None) +} + + +def setup(app): + app.add_node(youtube, **_NODE_VISITORS) + app.add_directive("youtube", YouTube) diff --git a/docs/source/writing-docs.rst b/docs/source/writing-docs.rst new file mode 100644 index 0000000..73123ff --- /dev/null +++ b/docs/source/writing-docs.rst @@ -0,0 +1,297 @@ +===================== +Writing Documentation +===================== + +In this section you will: + +* Generate HTML documentation using Sphinx, starting from a working example + provided by the cookiecutter template. +* Edit ``usage.rst`` to add API documentation and narrative documentation. +* Learn how to incorporate code examples, IPython examples, matplotlib plots, + and typeset math. + +Build the docs +-------------- + +Almost all scientific Python projects use the +`Sphinx documentation generator `_. +The cookiecutter template provided a working example with some popular +extensions installed and some sample pages. + +.. code-block:: none + + example/ + (...) + ├── docs + │   ├── Makefile + │   ├── build + │   ├── make.bat + │   └── source + │   ├── _static + │   │   └── .placeholder + │   ├── _templates + │   ├── conf.py + │   ├── index.rst + │   ├── installation.rst + │   ├── release-history.rst + │   └── usage.rst + (...) + +The ``.rst`` files are source code for our documentation. To build HTML pages +from this source, run: + +.. code-block:: bash + + make -C docs html + +You should see some log message ending in ``build succeeded.`` + +This output HTML will be located in ``docs/build/html``. In your Internet +browser, open ``file://.../docs/build/html/index.html``, where ``...`` is the +path to your project directory. If you aren't sure sure where that is, type +``pwd``. + +Update the docs +--------------- + +The source code for the documentation is located in ``docs/source/``. +Sphinx uses a markup language called ReStructured Text (.rst). We refer you to +`this primer `_ +to learn how to denote headings, links, lists, cross-references, etc. + +Sphinx formatting is sensitive to whitespace and generally quite picky. We +recommend running ``make -C docs html`` often to check that the documentation +builds successfully. Remember to commit your changes to git periodically. + +Good documentation includes both: + +* API (Application Programming Interface) documentation, listing every public + object in the library and its usage +* Narrative documentation interleaving prose and code examples to explain how + and why a library is meant to be used + +API Documentation +----------------- + +Most the work of writing good API documentation goes into writing good, +accurate docstrings. Sphinx can scrape that content and generate HTML from it. +Again, most scientific Python libraries use the +`numpydoc standard `_, +which looks like this: + +.. literalinclude:: refraction.py + +Autodoc +^^^^^^^ + +In an rst file, such as ``docs/source/usage.rst``, we can write: + +.. code-block:: rst + + .. autofunction:: example.refraction.snell + +which renders in HTML like so: + +.. autofunction:: example.refraction.snell + :noindex: + +From here we refer you to the +`sphinx autodoc documentation `_. + +Autosummary +^^^^^^^^^^^ + +If you have many related objects to document, it may be better to display them +in a table. Each row will include the name, the signature (optional), and the +one-line description from the docstring. + +In rst we can write: + +.. code-block:: rst + + .. autosummary:: + :toctree: generated/ + + example.refraction.snell + +which renders in HTML like so: + +.. autosummary:: + :toctree: generated/ + + example.refraction.snell + +It links to the full rendered docstring on a separate page that is +automatically generated. + +From here we refer you to the +`sphinx autosummary documentation `_. + +Narrative Documentation +----------------------- + +Code Blocks +^^^^^^^^^^^ + +Code blocks can be interspersed with narrative text like this: + +.. code-block:: rst + + Scientific libraries conventionally use radians. Numpy provides convenience + functions for converting between radians and degrees. + + .. code-block:: python + + import numpy as np + + + np.deg2rad(90) # pi / 2 + np.rad2deg(np.pi / 2) # 90.0 + +which renders in HTML as: + +Scientific libraries conventionally use radians. Numpy provides convenience +functions for converting between radians and degrees. + +.. code-block:: python + + import numpy as np + + + np.deg2rad(90) # pi / 2 + np.rad2deg(np.pi / 2) # 90.0 + +To render short code expressions inline, surround them with back-ticks. This: + +.. code-block:: rst + + Try ``snell(0, 1, 1.33)``. + +renders in HTML as: + + +Try ``snell(0, 1, 1.33)``. + +Embedded Scripts +^^^^^^^^^^^^^^^^ + +For lengthy examples with tens of lines or more, it can be convenient to embed +the content of a .py file rather than writing it directly into the +documentation. + +This can be done using the directive + +.. code-block:: rest + + .. literalinclude:: examples/some_example.py + +where the path is given relative to the current file's path. Thus, relative to +the repository's root directory, the path to this example script would be +``docs/source/examples/some_example.py``. + +From here we refer you to the +`sphinx code example documentation `_. + +To go beyond embedded scripts to a more richly-featured example gallery that +shows scripts and their outputs, we encourage you to look at +`sphinx-gallery `_. + +IPython Examples +^^^^^^^^^^^^^^^^ + +IPython's sphinx extension, which is included by the cookiecutter template, +makes it possible to execute example code and capture its output when the +documentation is built. This rst code: + +.. code-block:: rst + + .. ipython:: python + + 1 + 1 + +renders in HTML as: + +.. ipython:: python + + 1 + 1 + +From here we refer you to the +`IPython sphinx directive documentation `_. + +Plots +^^^^^ + +Matplotlib's sphinx extension, which is included by the cookiecutter template, +makes it possible to display matplotlib figures in line. This rst code: + +.. code-block:: rst + + .. plot:: + + import matplotlib.pyplot as plt + fig, ax = plt.subplots() + ax.plot([1, 1, 2, 3, 5, 8]) + +renders in HTML as: + +.. plot:: + + import matplotlib.pyplot as plt + fig, ax = plt.subplots() + ax.plot([1, 1, 2, 3, 5, 8]) + +From here we refer you to the +`matplotlib plot directive documentation `_. + +Math (LaTeX) +^^^^^^^^^^^^ + +Sphinx can render LaTeX typeset math in the browser (using +`MathJax `_). This rst code: + +.. code-block:: rst + + .. math:: + + \int_0^a x\,dx = \frac{1}{2}a^2 + +renders in HTML as: + +.. math:: + + \int_0^a x\,dx = \frac{1}{2}a^2 + +This notation can also be used in docstrings. For example, we could add +the equation of Snell's Law to the docstring of +:func:`~example.refraction.snell`. + +Math can also be written inline. This rst code: + +.. code-block:: rst + + The value of :math:`\pi` is 3.141592653.... + +renders in HTML as: + + The value of :math:`\pi` is 3.141592653.... + +Referencing Documented Objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can create links to documented functions like so: + +.. code-block:: rst + + The :func:`example.refraction.snell` function encodes Snell's Law. + +The :func:`example.refraction.snell` function encodes Snell's Law. + +Adding a ``~`` omits the module path from the link text. + +.. code-block:: rst + + The :func:`~example.refraction.snell` function encodes Snell's Law. + +The :func:`~example.refraction.snell` function encodes Snell's Law. + +See `the Sphinx documentation `_ for more. diff --git a/run_cookiecutter_example.py b/run_cookiecutter_example.py index b991e83..d7cf9c6 100644 --- a/run_cookiecutter_example.py +++ b/run_cookiecutter_example.py @@ -1,19 +1,31 @@ #!/usr/bin/env python3 import pexpect +full_name = 'Simon J.L. Billinge group' +email = 'simon.billinge@gmail.com' +github_username = 'diffpy' +project_name = 'diffpy.utils' + +project_short_name = project_name.rsplit('.', 1)[-1] p = pexpect.spawn('cookiecutter .') p.expect('full_name .*') -p.sendline('Brookhaven National Lab') +p.sendline(full_name) p.expect('email .*') -p.sendline('dallan@bnl.gov') +p.sendline(email) p.expect('github_username .*') -p.sendline('danielballan') +p.sendline(github_username) p.expect('project_name .*') -p.sendline('Example') +p.sendline(project_name) + +p.expect('project_slug .*') +p.sendline('') + +p.expect('project_short_name .*') +p.sendline(project_short_name) p.expect('package_dist_name .*') p.sendline('') @@ -25,7 +37,7 @@ p.sendline('') p.expect('project_short_description .*') -p.sendline('') +p.sendline('Shared utilities for diffpy packages.') p.expect('Select minimum_supported_python_version.*') p.sendline('') diff --git a/{{ cookiecutter.repo_name }}/.codecov.yml b/{{ cookiecutter.repo_name }}/.codecov.yml index 20ad66b..03afe9c 100644 --- a/{{ cookiecutter.repo_name }}/.codecov.yml +++ b/{{ cookiecutter.repo_name }}/.codecov.yml @@ -1,10 +1,34 @@ -# show coverage in CI status, not as a comment. -comment: off +# codecov can find this file anywhere in the repo, so we don't need to clutter +# the root folder. +#comment: false + +codecov: + notify: + require_ci_to_pass: no + coverage: status: - project: - default: - target: auto patch: default: + target: '70' + if_no_uploads: error + if_not_found: success + if_ci_failed: failure + project: + default: false + library: target: auto + if_no_uploads: error + if_not_found: success + if_ci_failed: failure + paths: '!*/tests/.*' + + tests: + target: 97.9% + paths: '*/tests/.*' + if_not_found: success + +flags: + tests: + paths: + - tests/ diff --git a/{{ cookiecutter.repo_name }}/.flake8 b/{{ cookiecutter.repo_name }}/.flake8 index 9308bc0..0b351c0 100644 --- a/{{ cookiecutter.repo_name }}/.flake8 +++ b/{{ cookiecutter.repo_name }}/.flake8 @@ -5,8 +5,9 @@ exclude = build, dist, versioneer.py, - {{ cookiecutter.package_dir_name }}/_version.py, - docs/source/conf.py + labpdfproc/_version.py, + doc/manual/source/conf.py max-line-length = 115 -# Ignore some style 'errors' produced while formatting by 'black' -ignore = E203, W503 +# Ignore some style 'errors' produced while formatting by 'black' (see link below) +# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings +extend-ignore = E203 diff --git a/{{ cookiecutter.repo_name }}/.github/workflows/docs.yml b/{{ cookiecutter.repo_name }}/.github/workflows/docs.yml index ea631f1..a4661f5 100644 --- a/{{ cookiecutter.repo_name }}/.github/workflows/docs.yml +++ b/{{ cookiecutter.repo_name }}/.github/workflows/docs.yml @@ -2,58 +2,42 @@ name: Build Documentation on: push: - pull_request: - workflow_dispatch: + branches: + - main + release: jobs: - build_docs: - # pull requests are a duplicate of a branch push if within the same repo. - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository - + test: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10"] - fail-fast: false - defaults: run: shell: bash -l {0} - steps: - - name: Set env vars - run: | - export REPOSITORY_NAME=${GITHUB_REPOSITORY#*/} # just the repo, as opposed to org/repo - echo "REPOSITORY_NAME=${REPOSITORY_NAME}" >> $GITHUB_ENV - - - name: Checkout the code - uses: actions/checkout@v3 + - uses: actions/checkout@v3 with: - fetch-depth: 1000 # should be enough to reach the most recent tag + fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + - uses: conda-incubator/setup-miniconda@v2 with: - python-version: ${{ matrix.python-version }} - - - name: Install documentation-building requirements - run: | - # For reference: https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html. - set -vxeuo pipefail + activate-environment: build + auto-update-conda: true - # These packages are installed in the base environment but may be older - # versions. Explicitly upgrade them because they often create - # installation problems if out of date. - python -m pip install --upgrade pip setuptools numpy + - name: install requirements + run: >- + conda install -n build -c conda-forge + --file requirements/build.txt + --file requirements/run.txt + --file requirements/docs.txt + --quiet --yes - pip install . - pip install -r requirements-dev.txt - pip list + - name: install the package + run: python -m pip install . --no-deps - - name: Build Docs - run: make -C docs/ html + - name: build documents + run: make -C doc html - - uses: actions/upload-artifact@v3 + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 with: - name: ${{ env.REPOSITORY_NAME }}-docs - path: docs/build/html/ + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./doc/build/html diff --git a/{{ cookiecutter.repo_name }}/.github/workflows/main.yml b/{{ cookiecutter.repo_name }}/.github/workflows/main.yml index 1841f78..950e658 100644 --- a/{{ cookiecutter.repo_name }}/.github/workflows/main.yml +++ b/{{ cookiecutter.repo_name }}/.github/workflows/main.yml @@ -1,59 +1,55 @@ -name: Unit Tests +name: CI on: push: + branches: + - main + - CI pull_request: workflow_dispatch: - # schedule: - # - cron: '00 4 * * *' # daily at 4AM jobs: - run_tests: - # pull requests are a duplicate of a branch push if within the same repo. - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository - - runs-on: ${{ matrix.host-os }} + miniconda: + name: Miniconda ${{ matrix.os }} + runs-on: ${{ matrix.os }} strategy: - matrix: - host-os: ["ubuntu-latest"] - # host-os: ["ubuntu-latest", "macos-latest", "windows-latest"] - python-version: ["3.8", "3.9", "3.10"] - fail-fast: false - - defaults: - run: - shell: bash -l {0} - + matrix: + os: ["ubuntu-latest"] steps: - - name: Set env vars - run: | - export REPOSITORY_NAME=${GITHUB_REPOSITORY#*/} # just the repo, as opposed to org/repo - echo "REPOSITORY_NAME=${REPOSITORY_NAME}" >> $GITHUB_ENV - - - name: Checkout the code + - name: check out {{ cookiecutter.project_name }} uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies + repository: {{ cookiecutter.github_username }}/{{ cookiecutter.project_name }} + path: . + fetch-depth: 0 # avoid shallow clone with no tags + + - name: initialize miniconda + # this uses a marketplace action that sets up miniconda in a way that makes + # it easier to use. I tried setting it up without this and it was a pain + uses: conda-incubator/setup-miniconda@v2 + with: + activate-environment: test + # environment.yml file is needed by this action. Because I don't want + # maintain this but rather maintain the requirements files it just has + # basic things in it like conda and pip + environment-file: ./environment.yml + python-version: 3 + auto-activate-base: false + + - name: install {{ cookiecutter.project_name }} requirements + shell: bash -l {0} run: | - # For reference: https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html. - set -vxeuo pipefail - - # These packages are installed in the base environment but may be older - # versions. Explicitly upgrade them because they often create - # installation problems if out of date. - python -m pip install --upgrade pip setuptools numpy - + conda config --set always_yes yes --set changeps1 no + conda config --add channels conda-forge + conda activate test + conda install --file requirements/run.txt + conda install --file requirements/test.txt pip install . - pip install -r requirements-dev.txt - pip list - - name: Test with pytest + - name: Validate {{ cookiecutter.project_name }} + shell: bash -l {0} run: | - set -vxeuo pipefail - coverage run -m pytest -vv -s + conda activate test + coverage run run_tests.py coverage report -m + codecov diff --git a/{{ cookiecutter.repo_name }}/.gitignore b/{{ cookiecutter.repo_name }}/.gitignore index 818e042..313f3b8 100644 --- a/{{ cookiecutter.repo_name }}/.gitignore +++ b/{{ cookiecutter.repo_name }}/.gitignore @@ -1,82 +1,46 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ *.py[cod] -*$py.class # C extensions *.so -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -venv/ -*.egg-info/ -.installed.cfg +# Packages *.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec +*.egg-info +dist +build +eggs +parts +bin +var +sdist +temp +develop-eggs +.installed.cfg +lib +lib64 +tags +errors.err # Installer logs pip-log.txt -pip-delete-this-directory.txt +MANIFEST # Unit test / coverage reports -htmlcov/ -.tox/ .coverage -.coverage.* -.cache +.tox nosetests.xml -coverage.xml -*,cover -.hypothesis/ # Translations *.mo -*.pot - -# Django stuff: -*.log - -# Sphinx documentation -docs/build/ -docs/source/generated/ - -# pytest -.pytest_cache/ - -# PyBuilder -target/ - -# Editor files -# mac -.DS_Store -*~ - -# vim -*.swp -*.swo -# pycharm -.idea/ +# Mr Developer +.mr.developer.cfg +.project +.pydevproject -# VSCode -.vscode/ +# version information +setup.cfg +/src/diffpy/*/version.cfg -# Ipython Notebook -.ipynb_checkpoints +# Rever +rever/ diff --git a/{{ cookiecutter.repo_name }}/.pre-commit-config.yaml b/{{ cookiecutter.repo_name }}/.pre-commit-config.yaml index 9a4c65b..c458806 100644 --- a/{{ cookiecutter.repo_name }}/.pre-commit-config.yaml +++ b/{{ cookiecutter.repo_name }}/.pre-commit-config.yaml @@ -1,26 +1,43 @@ default_language_version: python: python3 +ci: + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit hooks + autofix_prs: true + autoupdate_branch: 'pre-commit-autoupdate' + autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate' + autoupdate_schedule: monthly + skip: [no-commit-to-branch] + submodules: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/ambv/black - rev: 23.3.0 + exclude: '\.(rst|txt)$' + - repo: https://github.com/psf/black + rev: 24.4.2 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + rev: 7.0.0 hooks: - id: flake8 - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/kynan/nbstripout - rev: 0.6.1 + rev: 0.7.1 hooks: - id: nbstripout + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: no-commit-to-branch + name: Prevent Commit to Main Branch + args: ["--branch", "main"] + stages: [pre-commit] diff --git a/{{ cookiecutter.repo_name }}/LICENSE b/{{ cookiecutter.repo_name }}/LICENSE new file mode 100644 index 0000000..f6d92af --- /dev/null +++ b/{{ cookiecutter.repo_name }}/LICENSE @@ -0,0 +1,137 @@ +OPEN SOURCE LICENSE AGREEMENT +============================= + +Copyright (c) 2009-2011, University of Tennessee +Copyright (c) 1989, 1991 Free Software Foundation, Inc. +Copyright (c) 2006, The Regents of the University of California through + Lawrence Berkeley National Laboratory +Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") +Copyright (c) 2006-2007, Board of Trustees of Michigan State University +Copyright (c) 2008-2012, The Trustees of Columbia University in the City + of New York + +Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National + Laboratory + + +The "DiffPy-CMI" is distributed subject to the following license conditions: + + +SOFTWARE LICENSE AGREEMENT + + Software: DiffPy-CMI + + +(1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either +source code, or binary form and accompanying documentation). + +Part of the software was derived from the DANSE, ObjCryst++ (with permission), +PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of +which the original Copyrights are contained in each individual file. + +Each licensee is addressed as "you" or "Licensee." + + +(2) The copyright holders shown above and their third-party Licensors hereby +grant licensee a royalty-free nonexclusive license, subject to the limitations +stated herein and U.S. Government license rights. + + +(3) You may modify and make a copy or copies of the software for use within +your organization, if you meet the following conditions: + + (a) Copies in source code must include the copyright notice and this + software license agreement. + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy. + + +(4) You may modify a copy or copies of the Software or any portion of it, thus +forming a work based on the Software, and distribute copies of such work +outside your organization, if you meet all of the following conditions: + + (a) Copies in source code must include the copyright notice and this + Software License Agreement; + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy; + + (c) Modified copies and works based on the Software must carry prominent + notices stating that you changed specified portions of the Software. + + (d) Neither the name of Brookhaven Science Associates or Brookhaven + National Laboratory nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + written permission. + + +(5) Portions of the Software resulted from work developed under a U.S. +Government contract and are subject to the following license: +The Government is granted for itself and others acting on its behalf a +paid-up, nonexclusive, irrevocable worldwide license in this computer software +to reproduce, prepare derivative works, and perform publicly and display +publicly. + + +(6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT +WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY +LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND +THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL +LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF +THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE +PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION +UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. + + +(7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR +THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF +ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING +BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, +WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING +NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS +BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. + + +Brookhaven National Laboratory Notice +===================================== + +Acknowledgment of sponsorship +----------------------------- + +This software was produced by the Brookhaven National Laboratory, under +Contract DE-AC02-98CH10886 with the Department of Energy. + + +Government disclaimer of liability +---------------------------------- + +Neither the United States nor the United States Department of Energy, nor +any of their employees, makes any warranty, express or implied, or assumes +any legal liability or responsibility for the accuracy, completeness, or +usefulness of any data, apparatus, product, or process disclosed, or +represents that its use would not infringe privately owned rights. + + +Brookhaven disclaimer of liability +---------------------------------- + +Brookhaven National Laboratory makes no representations or warranties, +express or implied, nor assumes any liability for the use of this software. + + +Maintenance of notice +--------------------- + +In the interest of clarity regarding the origin and status of this +software, Brookhaven National Laboratory requests that any recipient of it +maintain this notice affixed to any distribution by the recipient that +contains a copy or derivative of this software. + + +END OF LICENSE diff --git a/{{ cookiecutter.repo_name }}/README.rst b/{{ cookiecutter.repo_name }}/README.rst index f2d03ce..5008242 100644 --- a/{{ cookiecutter.repo_name }}/README.rst +++ b/{{ cookiecutter.repo_name }}/README.rst @@ -3,18 +3,18 @@ {{ cookiecutter.project_name }} {{ section_separator }} -.. image:: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.repo_name }}/actions/workflows/testing.yml/badge.svg - :target: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.repo_name }}/actions/workflows/testing.yml +.. image:: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_name }}/actions/workflows/testing.yml/badge.svg + :target: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_name }}/actions/workflows/testing.yml -.. image:: https://img.shields.io/pypi/v/{{ cookiecutter.repo_name }}.svg - :target: https://pypi.python.org/pypi/{{ cookiecutter.repo_name }} +.. image:: https://img.shields.io/pypi/v/{{ cookiecutter.project_name }}.svg + :target: https://pypi.python.org/pypi/{{ cookiecutter.project_name }} {{ cookiecutter.project_short_description}} * Free software: 3-clause BSD license -* Documentation: (COMING SOON!) https://{{ cookiecutter.github_username}}.github.io/{{ cookiecutter.repo_name }}. +* Documentation: (COMING SOON!) https://{{ cookiecutter.github_username}}.github.io/{{ cookiecutter.project_name }}. Features -------- diff --git a/{{ cookiecutter.repo_name }}/docs/Makefile b/{{ cookiecutter.repo_name }}/docs/Makefile new file mode 100644 index 0000000..b041b78 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/docs/Makefile @@ -0,0 +1,193 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/{{ cookiecutter.project_slug }}.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/{{ cookiecutter.project_slug }}.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/{{ cookiecutter.project_slug }}" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/{{ cookiecutter.project_slug }}" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +# Manual publishing to the gh-pages branch + +GITREPOPATH = $(shell cd $(CURDIR) && git rev-parse --git-dir) +GITREMOTE = origin +GITREMOTEURL = $(shell git config --get remote.$(GITREMOTE).url) +GITLASTCOMMIT = $(shell git rev-parse --short HEAD) + +publish: + @test -d build/html || \ + ( echo >&2 "Run 'make html' first!"; false ) + git show-ref --verify --quiet refs/heads/gh-pages || \ + git branch --track gh-pages $(GITREMOTE)/gh-pages + test -d build/gh-pages || \ + git clone -s -b gh-pages $(GITREPOPATH) build/gh-pages + cd build/gh-pages && \ + git pull $(GITREMOTEURL) gh-pages + rsync -acv --delete --exclude=.git --exclude=.rsync-exclude \ + --exclude-from=build/gh-pages/.rsync-exclude \ + --link-dest=$(CURDIR)/build/html build/html/ build/gh-pages/ + cd build/gh-pages && \ + git add --all . && \ + git diff --cached --quiet || \ + git commit -m "Sync with the source at $(GITLASTCOMMIT)." + cd build/gh-pages && \ + git push origin gh-pages diff --git a/{{ cookiecutter.repo_name }}/docs/source/conf.py b/{{ cookiecutter.repo_name }}/docs/source/conf.py new file mode 100644 index 0000000..fdc7469 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/docs/source/conf.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# {{ cookiecutter.project_name }} documentation build configuration file, created by +# sphinx-quickstart on Thu Jan 30 15:49:41 2014. +# +# 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. + +import os +import sys +import time +from importlib.metadata import version + +# 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. +# sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath("../..")) +sys.path.insert(0, os.path.abspath("../../src")) + +# abbreviations +ab_authors = "Pavol Juhás, Timur Davis, Christopher L. Farrow, Simon J.L. Billinge group" + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.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.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "m2r", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = [".rst", ".md"] + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = "{{ cookiecutter.project_name }}" +copyright = "%Y, {{ cookiecutter.full_name }}" + +# 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. + +fullversion = version(project) +# The short X.Y version. +version = "".join(fullversion.split(".post")[:1]) +# The full version, including alpha/beta/rc tags. +release = fullversion + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +today = time.strftime("%B %d, %Y", time.localtime()) +year = today.split()[-1] +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' +# substitute YEAR in the copyright string +copyright = copyright.replace("%Y", year) + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ["build"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +modindex_common_prefix = ["{{ cookiecutter.project_name }}"] + +# Display all warnings for missing links. +nitpicky = True + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# 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 = { + "navigation_with_keys": "true", +} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# 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 + +# 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'] + +# 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 = [] + +# 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' + +# 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 = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is 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 = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = "{{ cookiecutter.project_slug }}"+"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': '', +} + +# 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 = [ + ("index", "{{ cookiecutter.project_name }}.tex", "{{ cookiecutter.project_name }} Documentation", ab_authors, "manual"), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [("index", "{{ cookiecutter.project_name }}", "{{ cookiecutter.project_name }}" + "Documentation", ab_authors, 1)] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + "index", + "{{ cookiecutter.project_name }}", + "{{ cookiecutter.project_name }}" + " Documentation", + ab_authors, + "{{ cookiecutter.project_name }}", + "One line description of project.", + "Miscellaneous", + ), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +# intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/{{ cookiecutter.repo_name }}/docs/source/license.rst b/{{ cookiecutter.repo_name }}/docs/source/license.rst new file mode 100644 index 0000000..38f27ee --- /dev/null +++ b/{{ cookiecutter.repo_name }}/docs/source/license.rst @@ -0,0 +1,144 @@ +:tocdepth: -1 + +.. index:: license + +License +####### + +OPEN SOURCE LICENSE AGREEMENT +============================= + +| Copyright (c) 2009-2011, University of Tennessee +| Copyright (c) 1989, 1991 Free Software Foundation, Inc. +| Copyright (c) 2006, The Regents of the University of California through + Lawrence Berkeley National Laboratory +| Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") +| Copyright (c) 2006-2007, Board of Trustees of Michigan State University +| Copyright (c) 2008-2012, The Trustees of Columbia University in + the City of New York +| Copyright (c) 2014-2019, Brookhaven Science Associates, + Brookhaven National Laboratory + + +The "DiffPy-CMI" is distributed subject to the following license conditions: + + +SOFTWARE LICENSE AGREEMENT +========================== + +Software: **DiffPy-CMI** + + +(1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either +source code, or binary form and accompanying documentation). + +Part of the software was derived from the DANSE, ObjCryst++ (with permission), +PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of +which the original Copyrights are contained in each individual file. + +Each licensee is addressed as "you" or "Licensee." + + +(2) The copyright holders shown above and their third-party Licensors hereby +grant licensee a royalty-free nonexclusive license, subject to the limitations +stated herein and U.S. Government license rights. + + +(3) You may modify and make a copy or copies of the software for use within +your organization, if you meet the following conditions: + + (a) Copies in source code must include the copyright notice and this + software license agreement. + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy. + + +(4) You may modify a copy or copies of the Software or any portion of it, thus +forming a work based on the Software, and distribute copies of such work +outside your organization, if you meet all of the following conditions: + + (a) Copies in source code must include the copyright notice and this + Software License Agreement; + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy; + + (c) Modified copies and works based on the Software must carry prominent + notices stating that you changed specified portions of the Software. + + (d) Neither the name of Brookhaven Science Associates or Brookhaven + National Laboratory nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + written permission. + + +(5) Portions of the Software resulted from work developed under a U.S. +Government contract and are subject to the following license: +The Government is granted for itself and others acting on its behalf a +paid-up, nonexclusive, irrevocable worldwide license in this computer software +to reproduce, prepare derivative works, and perform publicly and display +publicly. + + +(6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT +WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY +LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND +THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL +LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF +THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE +PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION +UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. + + +(7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR +THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF +ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING +BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, +WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING +NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS +BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. + + +Brookhaven National Laboratory Notice +===================================== + +Acknowledgment of sponsorship +----------------------------- + +This software was produced by the Brookhaven National Laboratory, under +Contract DE-AC02-98CH10886 with the Department of Energy. + + +Government disclaimer of liability +---------------------------------- + +Neither the United States nor the United States Department of Energy, nor +any of their employees, makes any warranty, express or implied, or assumes +any legal liability or responsibility for the accuracy, completeness, or +usefulness of any data, apparatus, product, or process disclosed, or +represents that its use would not infringe privately owned rights. + + +Brookhaven disclaimer of liability +---------------------------------- + +Brookhaven National Laboratory makes no representations or warranties, +express or implied, nor assumes any liability for the use of this software. + + +Maintenance of notice +--------------------- + +In the interest of clarity regarding the origin and status of this +software, Brookhaven National Laboratory requests that any recipient of it +maintain this notice affixed to any distribution by the recipient that +contains a copy or derivative of this software. + + +.. rubric:: END OF LICENSE diff --git a/{{ cookiecutter.repo_name }}/pyproject.toml b/{{ cookiecutter.repo_name }}/pyproject.toml index 3239179..d92d782 100644 --- a/{{ cookiecutter.repo_name }}/pyproject.toml +++ b/{{ cookiecutter.repo_name }}/pyproject.toml @@ -1,3 +1,55 @@ +[build-system] +requires = ["setuptools>=62.0", "setuptools-git-versioning<2"] +build-backend = "setuptools.build_meta" + +[project] +name = "{{ cookiecutter.project_name }}" +dynamic=['version'] +authors = [ + { name="{{ cookiecutter.full_name }}", email="{{ cookiecutter.email }}" }, +] +maintainers = [ + { name="{{ cookiecutter.full_name }}", email="{{ cookiecutter.email }}" }, +] +description = "{{ cookiecutter.project_short_description }}" +keywords = ["text data parsers", "wx grid", "diffraction objects"] #!!!! CHANGE THIS in cookiecutter.json +readme = "README.rst" +requires-python = ">=3.9" +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Topic :: Scientific/Engineering :: Physics', +] + +[project.urls] +Homepage = "https://github.com/diffpy/diffpy.utils/" #!!!! CHANGE THIS in cookiecutter.json +Issues = "https://github.com/diffpy/diffpy.utils/issues" #!!!! CHANGE THIS in cookiecutter.json + +[tool.setuptools-git-versioning] +enabled = true +template = "{tag}" +dev_template = "{tag}" +dirty_template = "{tag}" + +[tool.setuptools.packages.find] +# !!!!!! CHANGE THIS in cookiecutter.json +where = ["src"] # list of folders that contain the packages (["."] by default) +include = ["diffpy*"] # package names should match these glob patterns (["*"] by default) +exclude = ["diffpy.utils.tests*"] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) + [tool.black] line-length = 115 include = '\.pyi?$' @@ -8,6 +60,8 @@ exclude = ''' | \.mypy_cache | \.tox | \.venv + | \.rst + | \.txt | _build | buck-out | build diff --git a/{{ cookiecutter.repo_name }}/requirements-dev.txt b/{{ cookiecutter.repo_name }}/requirements-dev.txt new file mode 100644 index 0000000..3b388e1 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/requirements-dev.txt @@ -0,0 +1,19 @@ +# These are required for developing the package (running the tests, building +# the documentation) but not necessarily required for _using_ it. +black +codecov +coverage +flake8 +isort +nbstripout +pre-commit +pre-commit-hooks +pytest +sphinx +twine +# These are dependencies of various sphinx extensions for documentation. +ipython +matplotlib +numpydoc +sphinx-copybutton +sphinx_rtd_theme diff --git a/{{ cookiecutter.repo_name }}/requirements.txt b/{{ cookiecutter.repo_name }}/requirements.txt new file mode 100644 index 0000000..5289505 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/requirements.txt @@ -0,0 +1 @@ +# List required packages in this file, one per line. diff --git a/{{ cookiecutter.repo_name }}/setup.py b/{{ cookiecutter.repo_name }}/setup.py index 00d4663..356fb95 100644 --- a/{{ cookiecutter.repo_name }}/setup.py +++ b/{{ cookiecutter.repo_name }}/setup.py @@ -46,7 +46,7 @@ long_description=readme, author="{{ cookiecutter.full_name }}", author_email="{{ cookiecutter.email }}", - url="https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.repo_name }}", + url="https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_name }}", python_requires=">={}".format(".".join(str(n) for n in min_version)), packages=find_packages(exclude=["docs", "tests"]), entry_points={ diff --git a/{{ cookiecutter.repo_name }}/src/diffpy/__init__.py b/{{ cookiecutter.repo_name }}/src/diffpy/__init__.py new file mode 100644 index 0000000..f37a775 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/src/diffpy/__init__.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Pavol Juhas +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## + +"""diffpy - tools for structure analysis by diffraction. + +Blank namespace package. +""" + + +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) + + +# End of file diff --git a/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/__init__.py b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/__init__.py new file mode 100644 index 0000000..d07dd75 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/__init__.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.utils by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Chris Farrow, Pavol Juhas +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## + +"""Smaller shared functions for use by other diffpy packages. +""" + +# package version +package_name = '{{ cookiecutter.project_name }}' +version = getattr(__import__(package_name+".version", fromlist=["__version__"]), "__version__") + +# silence the pyflakes syntax checker +assert version or True + +# End of file diff --git a/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/tests/conftest.py b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/tests/conftest.py new file mode 100644 index 0000000..e3b6313 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/tests/conftest.py @@ -0,0 +1,19 @@ +import json +from pathlib import Path + +import pytest + + +@pytest.fixture +def user_filesystem(tmp_path): + base_dir = Path(tmp_path) + home_dir = base_dir / "home_dir" + home_dir.mkdir(parents=True, exist_ok=True) + cwd_dir = base_dir / "cwd_dir" + cwd_dir.mkdir(parents=True, exist_ok=True) + + home_config_data = {"username": "home_username", "email": "home@email.com"} + with open(home_dir / "diffpyconfig.json", "w") as f: + json.dump(home_config_data, f) + + yield tmp_path diff --git a/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/tests/debug.py b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/tests/debug.py new file mode 100644 index 0000000..272ce57 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/tests/debug.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.utils Complex Modeling Initiative +# (c) 2016 Brookhaven Science Associates, +# Brookhaven National Laboratory. +# All rights reserved. +# +# File coded by: Pavol Juhas +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE.txt for license information. +# +############################################################################## + +""" +Convenience module for debugging the unit tests using + +python -m diffpy.utils.tests.debug + +Exceptions raised by failed tests or other errors are not caught. +""" + + +if __name__ == "__main__": + import sys + package_name = '{{ cookiecutter.project_name }}' + testsuite = getattr(__import__(package_name+".tests", fromlist=["testsuite"]), "testsuite") + + pattern = sys.argv[1] if len(sys.argv) > 1 else "" + suite = testsuite(pattern) + suite.debug() + + +# End of file diff --git a/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/tests/run.py b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/tests/run.py new file mode 100644 index 0000000..b39a02e --- /dev/null +++ b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/tests/run.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.utils by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2012 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Pavol Juhas +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## + +"""Convenience module for executing all unit tests with + +python -m diffpy.utils.tests.run +""" + +if __name__ == "__main__": + import sys + + # show warnings by default + if not sys.warnoptions: + import os + import warnings + + warnings.simplefilter("default") + # also affect subprocesses + os.environ["PYTHONWARNINGS"] = "default" + + package_name = '{{ cookiecutter.project_name }}' + test = getattr(__import__(package_name+".tests", fromlist=["test"]), "test") + + # produce zero exit code for a successful test + sys.exit(not test().wasSuccessful()) + +# End of file diff --git a/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/version.py b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/version.py new file mode 100644 index 0000000..bebff2c --- /dev/null +++ b/{{ cookiecutter.repo_name }}/src/diffpy/{{ cookiecutter.project_short_name }}/version.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.utils by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2011 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Pavol Juhas +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## + +"""Definition of __version__.""" + +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] + +# obtain version information +from importlib.metadata import version + +__version__ = version("{{ cookiecutter.project_name}}") + +# End of file