├── .gitignore ├── requirements.txt ├── pyproject.toml ├── .tools ├── README ├── tools.py └── tests │ └── test_prompt.py ├── .github ├── pull_request_template.md └── PULL_REQUEST_TEMPLATE │ └── new_spec.md ├── README.md ├── steering-committee ├── spec-steering-committee-chair.toml ├── emeritus-spec-steering-committee.toml ├── spec-steering-committee.toml └── _index.md ├── core-projects ├── numpy.md ├── pysal.md ├── ipython.md ├── networkx.md ├── xarray.md ├── matplotlib.md ├── pandas.md ├── scipy.md ├── scikit-hep.md ├── scikit-image.md ├── scikit-learn.md ├── zarr.md ├── register.py └── _index.md ├── netlify.toml ├── Makefile ├── _index.md ├── .pre-commit-config.yaml ├── LICENSE ├── quickstart.py ├── spec-0005 └── index.md ├── spec-0007 ├── test_transition_to_rng.py ├── index.md └── transition_to_rng.py ├── spec-0000 ├── chart.md ├── index.md ├── SPEC0_versions.py └── schedule.md ├── spec-0002 └── index.md ├── spec-0006 └── index.md ├── spec-0003 └── index.md ├── spec-0004 └── index.md ├── spec-0009 └── index.md ├── spec-0008 └── index.md ├── spec-0001 └── index.md └── purpose-and-process └── _index.md /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#* 3 | **/__pycache__ 4 | .idea 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pre-commit>=4.0 2 | pandas>=2.2 3 | packaging>=24.2 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | target-version = "py313" 3 | exclude = ["spec-0007/transition_to_rng.py"] 4 | -------------------------------------------------------------------------------- /.tools/README: -------------------------------------------------------------------------------- 1 | This directory contains scripts and tools to be used for example for generating new SPEC documents from the template. 2 | 3 | Tools that support specific SPECs and/or their implementation should be located in the directory of the given SPEC. 4 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | If you are opening this PR to propose a new SPEC, please go to the `Preview` tab, 2 | then [click here for the new SPEC template](?expand=1&template=new_spec.md). 3 | Otherwise, delete this text and proceed with your PR as normal. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SPEC 2 | 3 | The Scientific Python Ecosystem Coordination (SPEC) mechanism is used to recommend 4 | project policies, coding conventions, and standard tooling. 5 | 6 | See https://scientific-python.org/specs/ for a list of SPECs and their statuses. 7 | -------------------------------------------------------------------------------- /steering-committee/spec-steering-committee-chair.toml: -------------------------------------------------------------------------------- 1 | [[item]] 2 | type = 'card' 3 | classcard = 'text-center' 4 | body = '''{{< image 5 | src="https://avatars.githubusercontent.com/u/123428?v=4" 6 | alt="Avatar of Jarrod Millman" 7 | >}} 8 | Jarrod Millman''' 9 | link = 'https://github.com/jarrodmillman' 10 | -------------------------------------------------------------------------------- /core-projects/numpy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "NumPy" 3 | avatar: https://avatars.githubusercontent.com/u/288276 4 | homepage: https://numpy.org 5 | repository: https://github.com/numpy/numpy 6 | pypi: https://pypi.org/project/numpy 7 | libraries-io: https://libraries.io/pypi/numpy 8 | license: https://github.com/numpy/numpy/blob/main/LICENSE.txt 9 | license-type: 3-clause BSD 10 | contact: melissawm 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/pysal.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "PySAL" 3 | avatar: https://avatars.githubusercontent.com/u/3769919 4 | homepage: https://pysal.org 5 | repository: https://github.com/pysal/pysal 6 | pypi: https://pypi.org/project/pysal 7 | libraries-io: https://libraries.io/pypi/pysal 8 | license: https://github.com/pysal/pysal/blob/main/LICENSE.txt 9 | license-type: 3-clause BSD 10 | contact: sjsrey 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/ipython.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "IPython" 3 | avatar: https://avatars.githubusercontent.com/u/230453 4 | homepage: https://ipython.org 5 | repository: https://github.com/ipython/ipython 6 | pypi: https://pypi.org/project/ipython 7 | libraries-io: https://libraries.io/pypi/ipython 8 | license: https://github.com/ipython/ipython/blob/master/LICENSE 9 | license-type: 3-clause BSD 10 | contact: Carreau ivanov 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/networkx.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "NetworkX" 3 | avatar: https://avatars.githubusercontent.com/u/388785 4 | homepage: https://networkx.org/ 5 | repository: https://github.com/networkx/networkx 6 | pypi: https://pypi.org/project/networkx 7 | libraries-io: https://libraries.io/pypi/networkx 8 | license: https://networkx.org/documentation/stable/license.html 9 | license-type: 3-clause BSD 10 | contact: rossbar 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/xarray.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "xarray" 3 | avatar: https://numfocus.org/wp-content/uploads/2018/09/xarray-logo-square.png 4 | homepage: https://xarray.dev/ 5 | repository: https://github.com/pydata/xarray 6 | pypi: https://pypi.org/project/xarray 7 | libraries-io: https://libraries.io/pypi/xarray 8 | license: https://github.com/pydata/xarray/blob/main/LICENSE 9 | license-type: Apache 2.0 10 | contact: andersy005 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/matplotlib.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Matplotlib" 3 | avatar: https://avatars.githubusercontent.com/u/215947 4 | homepage: https://matplotlib.org 5 | repository: https://github.com/matplotlib/matplotlib 6 | pypi: https://pypi.org/project/matplotlib 7 | libraries-io: https://libraries.io/pypi/matplotlib 8 | license: https://matplotlib.org/stable/users/license.html 9 | license-type: Matplotlib license 10 | contact: QuLogic 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/pandas.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "pandas" 3 | avatar: https://avatars.githubusercontent.com/u/21206976 4 | homepage: https://pandas.pydata.org/ 5 | repository: https://github.com/pandas-dev/pandas 6 | pypi: https://pypi.org/project/pandas 7 | libraries-io: https://libraries.io/pypi/pandas 8 | license: https://github.com/pandas-dev/pandas/blob/master/LICENSE 9 | license-type: 3-clause BSD 10 | contact: jorisvandenbossche 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/scipy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SciPy" 3 | avatar: https://raw.githubusercontent.com/scipy/archive/main/branding/logos/scipy_logo.svg 4 | homepage: https://scipy.org 5 | repository: https://github.com/scipy/scipy 6 | pypi: https://pypi.org/project/scipy 7 | libraries-io: https://libraries.io/pypi/scipy 8 | license: https://github.com/scipy/scipy/blob/main/LICENSE.txt 9 | license-type: 3-clause BSD 10 | contact: rgommers 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/scikit-hep.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Scikit-HEP" 3 | avatar: https://avatars.githubusercontent.com/u/23454624 4 | homepage: https://scikit-hep.org 5 | repository: https://github.com/scikit-hep/scikit-hep 6 | pypi: https://pypi.org/project/scikit-hep 7 | libraries-io: https://libraries.io/pypi/scikit-hep 8 | license: https://github.com/scikit-hep/scikit-hep/blob/main/LICENSE 9 | license-type: 3-clause BSD 10 | contact: matthewfeickert 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/scikit-image.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "scikit-image" 3 | avatar: https://avatars.githubusercontent.com/u/897180 4 | homepage: https://scikit-image.org 5 | repository: https://github.com/scikit-image/scikit-image 6 | pypi: https://pypi.org/project/scikit-image 7 | libraries-io: https://libraries.io/pypi/scikit-image 8 | license: https://github.com/scikit-image/scikit-image/blob/main/LICENSE.txt 9 | license-type: 3-clause BSD 10 | contact: jni 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/scikit-learn.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "scikit-learn" 3 | avatar: https://avatars.githubusercontent.com/u/365630 4 | homepage: https://scikit-learn.org 5 | repository: https://github.com/scikit-learn/scikit-learn 6 | pypi: https://pypi.org/project/scikit-learn 7 | libraries-io: https://libraries.io/pypi/scikit-learn 8 | license: https://github.com/scikit-learn/scikit-learn/blob/main/COPYING 9 | license-type: 3-clause BSD 10 | contact: thomasjpfan 11 | --- 12 | -------------------------------------------------------------------------------- /core-projects/zarr.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Zarr-Python" 3 | avatar: https://raw.githubusercontent.com/zarr-developers/zarr-logo/main/zarr-pink-stacked.svg 4 | homepage: https://zarr.dev 5 | repository: https://github.com/zarr-developers/zarr-python 6 | pypi: https://pypi.org/project/zarr 7 | libraries-io: https://libraries.io/pypi/zarr 8 | license: https://github.com/zarr-developers/zarr-python/blob/main/LICENSE.txt 9 | license-type: MIT 10 | contact: msankeys963 11 | --- 12 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build.environment] 2 | PYTHON_VERSION = "3.13" 3 | HUGO_VERSION = "0.152.2" 4 | DART_SASS_VERSION = "1.93.2" 5 | DART_SASS_URL = "https://github.com/sass/dart-sass/releases/download/" 6 | 7 | [build] 8 | base = "/" 9 | publish = "public" 10 | command = """\ 11 | export DART_SASS_TARBALL="dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" && \ 12 | (cd /tmp && \ 13 | curl -LJO ${DART_SASS_URL}/${DART_SASS_VERSION}/${DART_SASS_TARBALL} && \ 14 | tar -xf ${DART_SASS_TARBALL} && \ 15 | rm ${DART_SASS_TARBALL}) && \ 16 | export PATH=/tmp/dart-sass:$PATH && \ 17 | export PREVIEW_DEST="../build" && \ 18 | make preview-build && \ 19 | mv ../build/public . \ 20 | """ 21 | 22 | [[plugins]] 23 | package = "netlify-plugin-checklinks" 24 | [plugins.inputs] 25 | skipPatterns = ['https://fonts.gstatic.com', 'https://fonts.googleapis.com', 'https://cdn.jsdelivr.net'] 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREVIEW_DEST:=$(if $(PREVIEW_DEST),$(PREVIEW_DEST),/tmp/__specs.scientific-python.org_site-preview) 2 | HUGO_OPTS=--disableFastRender 3 | 4 | .PHONY: clean preview prepare-preview preview 5 | .DEFAULT_GOAL: preview-serve 6 | 7 | # Substitute the SPECs in specs.scientific-python.org with 8 | # those from this repository 9 | prepare-preview: clean 10 | mkdir -p $(PREVIEW_DEST) 11 | git clone https://github.com/scientific-python/scientific-python.org $(PREVIEW_DEST) 12 | git -C $(PREVIEW_DEST) submodule set-url themes/scientific-python-hugo-theme https://github.com/scientific-python/scientific-python-hugo-theme.git 13 | git -C $(PREVIEW_DEST) submodule update --init 14 | rm -rf $(PREVIEW_DEST)/content/specs/* 15 | cp -r * $(PREVIEW_DEST)/content/specs 16 | 17 | # Serve SPECs to http://localhost:1313 18 | preview-serve: prepare-preview 19 | cd $(PREVIEW_DEST) && make serve 20 | 21 | # Build website to $(PREVIEW_DEST)/public 22 | preview-build: prepare-preview 23 | cd $(PREVIEW_DEST) && make html 24 | 25 | clean: 26 | rm -rf $(PREVIEW_DEST) 27 | 28 | .PHONY: clean preview 29 | -------------------------------------------------------------------------------- /_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Scientific Python Ecosystem Coordination 3 | hide_meta: true 4 | --- 5 | 6 | SPECs provide recommendations for projects in the scientific Python ecosystem. 7 | Not all recommendations apply to all projects. 8 | All community members and ecosystem projects are welcome to participate in the SPEC process. 9 | The SPEC process is described in the 10 | [SPEC Purpose and Process](/specs/purpose-and-process), 11 | [SPEC Steering Committee](/specs/steering-committee), and 12 | [SPEC Core Projects](/specs/core-projects) documents. 13 | Community discussions take place on the 14 | [`SPECs` Discourse forum](https://discuss.scientific-python.org/c/specs/6). 15 | SPEC development takes place in the [SPEC repository](https://github.com/scientific-python/specs). 16 | 17 | If you want to **contribute a SPEC**, start by reading [SPEC Purpose and Process](/specs/purpose-and-process). 18 | Core projects may also want to [endorse a SPEC](/specs/purpose-and-process/#endorsing-a-spec). 19 | Contributors must adhere to our [code of conduct](https://scientific-python.org/code_of_conduct/). 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/new_spec.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Attestation 4 | 5 | - [ ] I have read and understood the [SPEC Purpose and Process](https://scientific-python.org/specs/purpose-and-process/) guidelines. 6 | 7 | ## Description 8 | 9 | 10 | 11 | ## Link to SPEC Committee's "viability" discussion thread (required) 12 | 13 | 17 | 18 | ## Description of (or link to) exploratory discussions/POCs (optional) 19 | 20 | 30 | -------------------------------------------------------------------------------- /.tools/tools.py: -------------------------------------------------------------------------------- 1 | def prompt( 2 | prompt_text, 3 | validate=lambda x: x if x else None, 4 | default=None, 5 | ): 6 | """Prompt the user for input. 7 | 8 | Parameters 9 | ---------- 10 | prompt_text : str 11 | Text displayed when prompting user for input. 12 | validate : callable, f(val) 13 | Called to validate the input. If this function raises or 14 | returns None, try again. 15 | default : any 16 | Accept empty user input, and return this value as a default. Note 17 | that this value is not validated. 18 | 19 | """ 20 | 21 | def valid(s): 22 | if isinstance(s, str): 23 | s = s.strip() 24 | 25 | if not s and (default is not None): 26 | return default 27 | 28 | try: 29 | value = validate(s) 30 | except Exception: 31 | value = None 32 | 33 | if value is None: 34 | print("Invalid input; please try again.") 35 | 36 | return value 37 | 38 | default_flag = f" [{default}]" if default is not None else "" 39 | while (answer := valid(input(f"{prompt_text}{default_flag}: "))) is None: 40 | pass 41 | 42 | return answer 43 | -------------------------------------------------------------------------------- /core-projects/register.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core Project Registration 3 | ========================= 4 | 5 | Register your core project. 6 | """ 7 | 8 | import sys 9 | import os 10 | 11 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../.tools")) 12 | from tools import prompt 13 | 14 | 15 | project = prompt("Project Name") 16 | project_lower = project.lower() 17 | logo = prompt("Project Logo (from your GitHub organization profile pic)") 18 | default_home = f"https://{project_lower}.org" 19 | homepage = prompt("Project Homepage", default=default_home) 20 | repository = prompt( 21 | "Repository", default=f"https://github.com/{project_lower}/{project_lower}" 22 | ) 23 | pypi = prompt("PyPI page", default=f"https://pypi.org/project/{project_lower}") 24 | license = prompt("License URL", default=f"{repository}/blob/main/LICENSE.txt") 25 | license_type = prompt("License", default="3-clause BSD") 26 | contact = prompt("List the GitHub handles of developers to contact about SPECs") 27 | 28 | filename = f"{project.lower()}.md" 29 | text = f"""--- 30 | title: "{project}" 31 | avatar: {logo} 32 | homepage: {homepage} 33 | repository: {repository} 34 | pypi: {pypi} 35 | libraries-io: https://libraries.io/pypi/{pypi.split("/")[-1]} 36 | license: {license} 37 | license-type: {license_type} 38 | contact: {contact} 39 | --- 40 | """ 41 | 42 | with open(filename, "w") as file: 43 | file.write(text) 44 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Install pre-commit hooks via 2 | # pre-commit install 3 | 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 7 | hooks: 8 | - id: trailing-whitespace 9 | - id: end-of-file-fixer 10 | - id: debug-statements 11 | - id: check-ast 12 | - id: mixed-line-ending 13 | - id: check-yaml 14 | args: [--allow-multiple-documents] 15 | - id: check-json 16 | - id: check-toml 17 | - id: check-added-large-files 18 | 19 | - repo: https://github.com/rbubley/mirrors-prettier 20 | rev: 5ba47274f9b181bce26a5150a725577f3c336011 # frozen: v3.6.2 21 | hooks: 22 | - id: prettier 23 | files: \.(md|yml|yaml) 24 | args: [--prose-wrap=preserve] 25 | 26 | - repo: https://github.com/astral-sh/ruff-pre-commit 27 | rev: 9c89adb347f6b973f4905a4be0051eb2ecf85dea # frozen: v0.13.3 28 | hooks: 29 | - id: ruff 30 | args: ["--fix", "--show-fixes", "--exit-non-zero-on-fix"] 31 | - id: ruff-format 32 | 33 | - repo: https://github.com/codespell-project/codespell 34 | rev: "63c8f8312b7559622c0d82815639671ae42132ac" # frozen: v2.4.1 35 | hooks: 36 | - id: codespell 37 | 38 | ci: 39 | autofix_prs: false 40 | autofix_commit_msg: "[pre-commit.ci 🤖] Apply code format tools to PR" 41 | autoupdate_schedule: quarterly 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021--2024, Scientific Python project 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /.tools/tests/test_prompt.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | from tools import prompt 4 | import builtins 5 | 6 | 7 | @contextmanager 8 | def yield_input(s): 9 | system_input = builtins.input 10 | 11 | def input(*args, **kwargs): 12 | return input.returnvals.pop(0) 13 | 14 | if isinstance(s, (list, tuple)): 15 | input.returnvals = list(s) 16 | else: 17 | input.returnvals = [s] 18 | 19 | builtins.input = input 20 | 21 | yield 22 | 23 | builtins.input = system_input 24 | 25 | 26 | def test_validate_int(): 27 | with yield_input(["foo", "1.5", 1]): 28 | val = prompt( 29 | "Please input integer", 30 | validate=lambda x: int(x), 31 | ) 32 | assert val == 1 33 | 34 | 35 | def test_validate_str(): 36 | with yield_input(["hello", "world", "scikit-something"]): 37 | val = prompt( 38 | "Input scikit name", 39 | validate=lambda x: x if x.startswith("scikit") else None, 40 | ) 41 | assert val == "scikit-something" 42 | 43 | 44 | def test_default_value(): 45 | with yield_input(""): 46 | val = prompt( 47 | "Enter optional string", 48 | default="default-answer", 49 | ) 50 | assert val == "default-answer" 51 | 52 | 53 | def test_default_value_after_bad_input(): 54 | with yield_input(["asd", ""]): 55 | val = prompt( 56 | "Enter scikit name", 57 | validate=lambda x: x if x.startswith("scikit") else None, 58 | default="scikit-something", 59 | ) 60 | assert val == "scikit-something" 61 | 62 | 63 | def test_default_not_validated(): 64 | with yield_input(""): 65 | val = prompt( 66 | "Enter a number", 67 | validate=lambda x: int(x), 68 | default="hello", 69 | ) 70 | assert val == "hello" 71 | -------------------------------------------------------------------------------- /quickstart.py: -------------------------------------------------------------------------------- 1 | """ 2 | SPEC Quickstart 3 | =============== 4 | 5 | Quickly setup stub for new SPEC. 6 | """ 7 | 8 | from datetime import datetime 9 | 10 | import sys 11 | import os 12 | 13 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".tools")) 14 | from tools import prompt 15 | 16 | 17 | now = datetime.now() 18 | author = prompt("Your Name") 19 | email = prompt("Your Email Address") 20 | number = prompt("SPEC number", validate=lambda x: int(x)) 21 | title = prompt("SPEC title") 22 | 23 | filename = f"spec-{number:04d}/index.md" 24 | text = f"""--- 25 | title: "SPEC {number} — {title}" 26 | number: {number} 27 | date: {now.strftime("%Y-%m-%d")} 28 | author: 29 | - "{author} <{email}>" 30 | is-draft: true 31 | endorsed-by: 32 | --- 33 | 34 | ## Description 35 | 36 | 39 | 40 | ### Core Project Endorsement 41 | 42 | 45 | 46 | ### Ecosystem Adoption 47 | 48 | 51 | 52 | #### Badges 53 | 54 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 55 | 56 | {{{{< spec_badge number="{number}" title="{title}" >}}}} 57 | 58 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 59 | 60 | ## Implementation 61 | 62 | 68 | 69 | ## Notes 70 | 71 | 75 | """ 76 | 77 | os.makedirs(os.path.dirname(filename), exist_ok=True) 78 | with open(filename, "w") as file: 79 | file.write(text) 80 | -------------------------------------------------------------------------------- /steering-committee/emeritus-spec-steering-committee.toml: -------------------------------------------------------------------------------- 1 | [[item]] 2 | type = 'card' 3 | classcard = 'text-center' 4 | body = '''{{< image 5 | src="https://avatars.githubusercontent.com/u/13301940?u=aa3bac002007d5212fe22a0baaadc25e078474ca&v=4" 6 | alt="Avatar of Anderson Banihirwe" 7 | >}} 8 | Anderson Banihirwe''' 9 | link = 'https://github.com/andersy005' 10 | 11 | [[item]] 12 | type = 'card' 13 | classcard = 'text-center' 14 | body = '''{{< image 15 | src="https://avatars.githubusercontent.com/u/7579677?u=1ebcc2526a9787b4786c9383870c6b8fbad217d8&v=4" 16 | alt="Avatar of Georgiana" 17 | >}} 18 | Georgiana''' 19 | link = 'https://github.com/GeorgianaElena' 20 | 21 | [[item]] 22 | type = 'card' 23 | classcard = 'text-center' 24 | body = '''{{< image 25 | src="https://avatars.githubusercontent.com/u/13029839?u=672ad3a5d17cec4ec6ff4a4b3302a4bef9535c8b&v=4" 26 | alt="Avatar of Julien Jerphanion" 27 | >}} 28 | Julien Jerphanion''' 29 | link = 'https://github.com/jjerphan' 30 | 31 | [[item]] 32 | type = 'card' 33 | classcard = 'text-center' 34 | body = '''{{< image 35 | src="https://avatars.githubusercontent.com/u/492549?v=4" 36 | alt="Avatar of Juan Nunez-Iglesias" 37 | >}} 38 | Juan Nunez-Iglesias''' 39 | link = 'https://github.com/jni' 40 | 41 | [[item]] 42 | type = 'card' 43 | classcard = 'text-center' 44 | body = '''{{< image 45 | src="https://avatars.githubusercontent.com/u/1020496?u=c30d768880284601483689157e4114351815044b&v=4" 46 | alt="Avatar of Joris Van den Bossche" 47 | >}} 48 | Joris Van den Bossche''' 49 | link = 'https://github.com/jorisvandenbossche' 50 | 51 | [[item]] 52 | type = 'card' 53 | classcard = 'text-center' 54 | body = '''{{< image 55 | src="https://avatars.githubusercontent.com/u/29165011?u=a9cad2941ed8ddec88ea9fc7523ff70433fce7e6&v=4" 56 | alt="Avatar of Kira Evans" 57 | >}} 58 | Kira Evans''' 59 | link = 'https://github.com/kne42' 60 | 61 | [[item]] 62 | type = 'card' 63 | classcard = 'text-center' 64 | body = '''{{< image 65 | src="https://avatars.githubusercontent.com/u/3487237?v=4" 66 | alt="Avatar of Kristen Thyng" 67 | >}} 68 | Kristen Thyng''' 69 | link = 'https://github.com/kthyng' 70 | 71 | [[item]] 72 | type = 'card' 73 | classcard = 'text-center' 74 | body = '''{{< image 75 | src="https://avatars.githubusercontent.com/u/23182829?u=847be7a6e1eb5d5198d61c7f5ae093d11f2f9763&v=4" 76 | alt="Avatar of Lucy Liu" 77 | >}} 78 | Lucy Liu''' 79 | link = 'https://github.com/lucyleeow' 80 | 81 | [[item]] 82 | type = 'card' 83 | classcard = 'text-center' 84 | body = '''{{< image 85 | src="https://avatars.githubusercontent.com/u/3949932?u=aacac68df60d2cf64c17c7e5aa17adf8b738aa7b&v=4" 86 | alt="Avatar of Melissa Weber Mendonça" 87 | >}} 88 | Melissa Weber Mendonça''' 89 | link = 'https://github.com/melissawm' 90 | 91 | [[item]] 92 | type = 'card' 93 | classcard = 'text-center' 94 | body = '''{{< image 95 | src="https://avatars.githubusercontent.com/u/2090236?v=4" 96 | alt="Avatar of P. L. Lim" 97 | >}} 98 | P. L. Lim''' 99 | link = 'https://github.com/pllim' 100 | -------------------------------------------------------------------------------- /spec-0005/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 5 — CI Best Practices" 3 | number: 5 4 | date: 2022-09-22 5 | author: 6 | - "Tim Head " 7 | - "Marta Iborra " 8 | - "Ryan May " 9 | - "Jarrod Millman " 10 | - "Brigitta Sipőcz " 11 | is-draft: true 12 | endorsed-by: 13 | --- 14 | 15 | ## Description 16 | 17 | 20 | 21 | ### Core Project Endorsement 22 | 23 | 26 | 27 | ### Ecosystem Adoption 28 | 29 | 32 | 33 | #### Badges 34 | 35 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 36 | {{< spec_badge number="5" title="CI Best Practices" >}} 37 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 38 | 39 | ## Implementation 40 | 41 | 47 | 48 | ### Build nightly wheels 49 | 50 | By building and uploading wheel for your project you make it easier for projects 51 | that depend on you to test against your latest changes. They can then report problems 52 | they find. 53 | 54 | There are a few steps to implementing this for your project: 55 | 56 | 1. Get access to the area that nightly wheels are uploaded to 57 | 2. Setup a CI step that builds wheels for your project 58 | 3. Setup a CI step that uploads wheels to https://anaconda.org/scientific-python-nightly-wheels/ 59 | 60 | For step (1) contact XXX. Deposit the token you get as a [secret on your repository](https://docs.github.com/en/actions/security-guides/encrypted-secrets) named `UPLOAD_TOKEN`. 61 | 62 | The work for step (2) depends on your project. You are probably already doing this for your 63 | releases. The new thing to add is that building wheels is run on a schedule every night or 64 | once a week. 65 | 66 | For step (3) there is a GitHub Action that you can use. You can find the action at 67 | https://github.com/scientific-python/upload-nightly-action. To use it in your "build wheels 68 | workflow" add the following lines as an additional step: 69 | 70 | ``` 71 | - name: Upload wheel 72 | uses: scientific-python/upload-nightly-action@main 73 | with: 74 | artifacts_path: dist/ 75 | anaconda_nightly_upload_token: ${{ secrets.UPLOAD_TOKEN }} 76 | ``` 77 | 78 | ## Notes 79 | 80 | 84 | 85 | - Recommend that CI running against PRs be expected to pass--if it fails, it should be important 86 | 87 | - Schedule regular runs of CI against nightlies/pre-releases 88 | - Good for other checks that don't need to run on every PR 89 | - Could use for pins 90 | - Can use labels to trigger additional runs of "exotic" jobs 91 | - Can open an issue on failure rather than hiding in a UI somewhere--helps give notifications 92 | - Generate a badge for the workflow, that can be collected into a dashboard 93 | 94 | - GitHub actions security concerns 95 | - Need to trust action creators 96 | - Pin to hash vs. version/tag? 97 | - 98 | -------------------------------------------------------------------------------- /spec-0007/test_transition_to_rng.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | import numpy as np 4 | import pytest 5 | 6 | from transition_to_rng import _transition_to_rng 7 | 8 | from scipy._lib._util import check_random_state 9 | 10 | 11 | @_transition_to_rng("random_state", position_num=1, end_version="1.17.0") 12 | def library_function(arg1, rng=None, arg2=0): 13 | rng = check_random_state(rng) 14 | return arg1, rng.random(), arg2 15 | 16 | 17 | @contextlib.contextmanager 18 | def np_random_seed(seed=0): 19 | # Save RandomState 20 | rs = np.random.mtrand._rand 21 | 22 | # Install temporary RandomState 23 | np.random.mtrand._rand = np.random.RandomState(seed) 24 | 25 | yield 26 | 27 | np.random.mtrand._rand = rs 28 | 29 | 30 | def test_positional_random_state(): 31 | # doesn't need to warn 32 | library_function(1, np.random.default_rng(2384924)) # Generators still accepted 33 | 34 | message = "Positional use of" 35 | if np.random.mtrand._rand._bit_generator._seed_seq is not None: 36 | library_function(1, None) # seed not set 37 | else: 38 | with pytest.warns(FutureWarning, match=message): 39 | library_function(1, None) # seed set 40 | 41 | with pytest.warns(FutureWarning, match=message): 42 | library_function(1, 1) # behavior will change 43 | 44 | with pytest.warns(FutureWarning, match=message): 45 | library_function(1, np.random.RandomState(1)) # will error 46 | 47 | with pytest.warns(FutureWarning, match=message): 48 | library_function(1, np.random) # will error 49 | 50 | 51 | def test_random_state_deprecated(): 52 | message = "keyword argument `random_state` is deprecated" 53 | 54 | with pytest.warns(DeprecationWarning, match=message): 55 | library_function(1, random_state=None) 56 | 57 | with pytest.warns(DeprecationWarning, match=message): 58 | library_function(1, random_state=1) 59 | 60 | 61 | def test_rng_correct_usage(): 62 | library_function(1, rng=None) 63 | 64 | rng = np.random.default_rng(1) 65 | ref_random = rng.random() 66 | 67 | res = library_function(1, rng=1) 68 | assert res == (1, ref_random, 0) 69 | 70 | rng = np.random.default_rng(1) 71 | res = library_function(1, rng, arg2=2) 72 | assert res == (1, ref_random, 2) 73 | 74 | 75 | def test_rng_incorrect_usage(): 76 | with pytest.raises(TypeError, match="SeedSequence expects"): 77 | library_function(1, rng=np.random.RandomState(123)) 78 | 79 | with pytest.raises(TypeError, match="multiple values"): 80 | library_function(1, rng=1, random_state=1) 81 | 82 | 83 | def test_seeded_vs_unseeded(): 84 | with np_random_seed(): 85 | with pytest.warns(FutureWarning, match="NumPy global RNG"): 86 | library_function(1) 87 | 88 | # These tests should still pass when the global seed is set, 89 | # since they provide explicit `random_state` or `rng` 90 | test_positional_random_state() 91 | test_random_state_deprecated() 92 | test_rng_correct_usage() 93 | 94 | # Entirely unseeded, should proceed without warning 95 | library_function(1) 96 | 97 | 98 | def test_decorator_no_positional(): 99 | @_transition_to_rng("random_state", end_version="1.17.0") 100 | def library_function(arg1, *, rng=None, arg2=None): 101 | rng = check_random_state(rng) 102 | return arg1, rng.random(), arg2 103 | 104 | message = "keyword argument `random_state` is deprecated" 105 | with pytest.warns(DeprecationWarning, match=message): 106 | library_function(1, random_state=3) 107 | 108 | library_function(1, rng=123) 109 | 110 | 111 | def test_decorator_no_end_version(): 112 | @_transition_to_rng("random_state") 113 | def library_function(arg1, rng=None, *, arg2=None): 114 | rng = check_random_state(rng) 115 | return arg1, rng.random(), arg2 116 | 117 | # no warnings emitted 118 | library_function(1, rng=np.random.default_rng(235498235)) 119 | library_function(1, random_state=np.random.default_rng(235498235)) 120 | library_function(1, 235498235) 121 | with np_random_seed(): 122 | library_function(1, None) 123 | 124 | 125 | if __name__ == "__main__": 126 | import pytest 127 | 128 | pytest.main(["-W", "error"]) 129 | -------------------------------------------------------------------------------- /spec-0000/chart.md: -------------------------------------------------------------------------------- 1 | gantt 2 | dateFormat YYYY-MM-DD 3 | axisFormat %m / %Y 4 | title Support Window 5 | 6 | section python 7 | 3.11 : 2022-10-24,2025-10-23 8 | 3.12 : 2023-10-02,2026-10-01 9 | 3.13 : 2024-10-07,2027-10-07 10 | 3.14 : 2025-10-07,2028-10-06 11 | 12 | section numpy 13 | 1.25.0 : 2023-06-17,2025-06-16 14 | 1.26.0 : 2023-09-16,2025-09-15 15 | 2.0.0 : 2024-06-16,2026-06-16 16 | 2.1.0 : 2024-08-18,2026-08-18 17 | 2.2.0 : 2024-12-08,2026-12-08 18 | 2.3.0 : 2025-06-07,2027-06-07 19 | 20 | section scipy 21 | 1.10.0 : 2023-01-03,2025-01-02 22 | 1.11.0 : 2023-06-25,2025-06-24 23 | 1.12.0 : 2024-01-20,2026-01-19 24 | 1.13.0 : 2024-04-02,2026-04-02 25 | 1.14.0 : 2024-06-24,2026-06-24 26 | 1.15.0 : 2025-01-03,2027-01-03 27 | 1.16.0 : 2025-06-22,2027-06-22 28 | 29 | section matplotlib 30 | 3.7.0 : 2023-02-13,2025-02-12 31 | 3.8.0 : 2023-09-15,2025-09-14 32 | 3.9.0 : 2024-05-15,2026-05-15 33 | 3.10.0 : 2024-12-14,2026-12-14 34 | 35 | section pandas 36 | 2.0.0 : 2023-04-03,2025-04-02 37 | 2.1.0 : 2023-08-30,2025-08-29 38 | 2.2.0 : 2024-01-20,2026-01-19 39 | 2.3.0 : 2025-06-05,2027-06-05 40 | 41 | section scikit-image 42 | 0.20.0 : 2023-02-28,2025-02-27 43 | 0.21.0 : 2023-06-02,2025-06-01 44 | 0.22.0 : 2023-10-03,2025-10-02 45 | 0.23.0 : 2024-04-10,2026-04-10 46 | 0.24.0 : 2024-06-18,2026-06-18 47 | 0.25.0 : 2024-12-13,2026-12-13 48 | 49 | section networkx 50 | 3.0 : 2023-01-08,2025-01-07 51 | 3.1 : 2023-04-04,2025-04-03 52 | 3.2 : 2023-10-19,2025-10-18 53 | 3.3 : 2024-04-06,2026-04-06 54 | 3.4 : 2024-10-10,2026-10-10 55 | 3.5 : 2025-05-29,2027-05-29 56 | 3.6 : 2025-11-24,2027-11-24 57 | 58 | section scikit-learn 59 | 1.3.0 : 2023-06-30,2025-06-29 60 | 1.4.0 : 2024-01-18,2026-01-17 61 | 1.5.0 : 2024-05-21,2026-05-21 62 | 1.6.0 : 2024-12-09,2026-12-09 63 | 1.7.0 : 2025-06-05,2027-06-05 64 | 65 | section xarray 66 | 2023.1.0 : 2023-01-18,2025-01-17 67 | 2023.2.0 : 2023-02-07,2025-02-06 68 | 2023.3.0 : 2023-03-22,2025-03-21 69 | 2023.4.0 : 2023-04-14,2025-04-13 70 | 2023.5.0 : 2023-05-19,2025-05-18 71 | 2023.6.0 : 2023-06-23,2025-06-22 72 | 2023.7.0 : 2023-07-17,2025-07-16 73 | 2023.8.0 : 2023-08-20,2025-08-19 74 | 2023.9.0 : 2023-09-26,2025-09-25 75 | 2023.10.0 : 2023-10-19,2025-10-18 76 | 2023.11.0 : 2023-11-17,2025-11-16 77 | 2023.12.0 : 2023-12-08,2025-12-07 78 | 2024.1.0 : 2024-01-17,2026-01-16 79 | 2024.2.0 : 2024-02-19,2026-02-18 80 | 2024.3.0 : 2024-03-29,2026-03-29 81 | 2024.5.0 : 2024-05-13,2026-05-13 82 | 2024.6.0 : 2024-06-13,2026-06-13 83 | 2024.7.0 : 2024-07-30,2026-07-30 84 | 2024.9.0 : 2024-09-11,2026-09-11 85 | 2024.10.0 : 2024-10-24,2026-10-24 86 | 2024.11.0 : 2024-11-22,2026-11-22 87 | 2025.1.0 : 2025-01-03,2027-01-03 88 | 2025.3.0 : 2025-03-20,2027-03-20 89 | 2025.4.0 : 2025-04-29,2027-04-29 90 | 2025.6.0 : 2025-06-10,2027-06-10 91 | 2025.7.0 : 2025-07-03,2027-07-03 92 | 2025.8.0 : 2025-08-14,2027-08-14 93 | 2025.9.0 : 2025-09-04,2027-09-04 94 | 2025.10.0 : 2025-10-06,2027-10-06 95 | 2025.11.0 : 2025-11-17,2027-11-17 96 | 97 | section ipython 98 | 8.8.0 : 2023-01-03,2025-01-02 99 | 8.9.0 : 2023-01-27,2025-01-26 100 | 8.10.0 : 2023-02-10,2025-02-09 101 | 8.11.0 : 2023-02-28,2025-02-27 102 | 8.12.0 : 2023-03-30,2025-03-29 103 | 8.13.0 : 2023-04-28,2025-04-27 104 | 8.14.0 : 2023-06-02,2025-06-01 105 | 8.15.0 : 2023-09-01,2025-08-31 106 | 8.16.0 : 2023-09-29,2025-09-28 107 | 8.17.0 : 2023-10-30,2025-10-29 108 | 8.18.0 : 2023-11-24,2025-11-23 109 | 8.19.0 : 2023-12-22,2025-12-21 110 | 8.20.0 : 2024-01-08,2026-01-07 111 | 8.21.0 : 2024-01-31,2026-01-30 112 | 8.22.0 : 2024-02-22,2026-02-21 113 | 8.23.0 : 2024-03-31,2026-03-31 114 | 8.24.0 : 2024-04-26,2026-04-26 115 | 8.25.0 : 2024-05-31,2026-05-31 116 | 8.26.0 : 2024-06-28,2026-06-28 117 | 8.27.0 : 2024-08-30,2026-08-30 118 | 8.28.0 : 2024-10-02,2026-10-02 119 | 8.29.0 : 2024-10-25,2026-10-25 120 | 8.30.0 : 2024-11-29,2026-11-29 121 | 8.31.0 : 2024-12-20,2026-12-20 122 | 8.32.0 : 2025-01-31,2027-01-31 123 | 8.33.0 : 2025-02-28,2027-02-28 124 | 8.34.0 : 2025-03-08,2027-03-08 125 | 8.35.0 : 2025-04-07,2027-04-07 126 | 8.36.0 : 2025-04-25,2027-04-25 127 | 8.37.0 : 2025-05-31,2027-05-31 128 | 9.0.0 : 2025-02-28,2027-02-28 129 | 9.1.0 : 2025-04-07,2027-04-07 130 | 9.2.0 : 2025-04-25,2027-04-25 131 | 9.3.0 : 2025-05-31,2027-05-31 132 | 9.4.0 : 2025-07-01,2027-07-01 133 | 9.5.0 : 2025-08-29,2027-08-29 134 | 9.6.0 : 2025-09-29,2027-09-29 135 | 9.7.0 : 2025-11-05,2027-11-05 136 | 137 | section zarr 138 | 2.14.0 : 2023-02-10,2025-02-09 139 | 2.15.0 : 2023-06-14,2025-06-13 140 | 2.16.0 : 2023-07-20,2025-07-19 141 | 2.17.0 : 2024-02-14,2026-02-13 142 | 2.18.0 : 2024-05-07,2026-05-07 143 | 3.0.0 : 2025-01-09,2027-01-09 144 | 3.1.0 : 2025-07-15,2027-07-15 145 | -------------------------------------------------------------------------------- /steering-committee/spec-steering-committee.toml: -------------------------------------------------------------------------------- 1 | [[item]] 2 | type = 'card' 3 | classcard = 'text-center' 4 | body = '''{{< image 5 | src="https://avatars.githubusercontent.com/u/6788290?u=d9a388224b87d55106cb3e6199d02ebc1d8e0553&v=4" 6 | alt="Avatar of Brigitta Sipőcz" 7 | >}} 8 | Brigitta Sipőcz''' 9 | link = 'https://github.com/bsipocz' 10 | 11 | [[item]] 12 | type = 'card' 13 | classcard = 'text-center' 14 | body = '''{{< image 15 | src="https://avatars.githubusercontent.com/u/335567?v=4" 16 | alt="Avatar of M Bussonnier" 17 | >}} 18 | M Bussonnier''' 19 | link = 'https://github.com/Carreau' 20 | 21 | [[item]] 22 | type = 'card' 23 | classcard = 'text-center' 24 | body = '''{{< image 25 | src="https://avatars.githubusercontent.com/u/7454015?u=d9704641f89c00ce86f8207e59aedb554109d7fa&v=4" 26 | alt="Avatar of Guillaume Lemaitre" 27 | >}} 28 | Guillaume Lemaitre''' 29 | link = 'https://github.com/glemaitre' 30 | 31 | [[item]] 32 | type = 'card' 33 | classcard = 'text-center' 34 | body = '''{{< image 35 | src="https://avatars.githubusercontent.com/u/43481325?u=8c0c0adbf3f2efd2cce72951d3554064c7bbfce3&v=4" 36 | alt="Avatar of Inessa Pawson" 37 | >}} 38 | Inessa Pawson''' 39 | link = 'https://github.com/InessaPawson' 40 | 41 | [[item]] 42 | type = 'card' 43 | classcard = 'text-center' 44 | body = '''{{< image 45 | src="https://avatars.githubusercontent.com/u/118211?u=df34f29e82134879f506869b2af3252e35db6bb4&v=4" 46 | alt="Avatar of Paul Ivanov" 47 | >}} 48 | Paul Ivanov''' 49 | link = 'https://github.com/ivanov' 50 | 51 | [[item]] 52 | type = 'card' 53 | classcard = 'text-center' 54 | body = '''{{< image 55 | src="https://avatars.githubusercontent.com/u/123428?v=4" 56 | alt="Avatar of Jarrod Millman" 57 | >}} 58 | Jarrod Millman''' 59 | link = 'https://github.com/jarrodmillman' 60 | 61 | [[item]] 62 | type = 'card' 63 | classcard = 'text-center' 64 | body = '''{{< image 65 | src="https://avatars.githubusercontent.com/u/18587879?u=83f10fabe466ae8e7052c03c5e6e9cde684a4e10&v=4" 66 | alt="Avatar of Juanita Gomez" 67 | >}} 68 | Juanita Gomez''' 69 | link = 'https://github.com/juanis2112' 70 | 71 | [[item]] 72 | type = 'card' 73 | classcard = 'text-center' 74 | body = '''{{< image 75 | src="https://avatars.githubusercontent.com/u/20140352?u=0f68b3b14560c1ac9a5b6cad15e2b8e5f5efc4d3&v=4" 76 | alt="Avatar of Lars Grüter" 77 | >}} 78 | Lars Grüter''' 79 | link = 'https://github.com/lagru' 80 | 81 | [[item]] 82 | type = 'card' 83 | classcard = 'text-center' 84 | body = '''{{< image 85 | src="https://avatars.githubusercontent.com/u/6570539?u=cfb3e218754e85c4fac18064d7cfdce0b67ddaa6&v=4" 86 | alt="Avatar of Matt Haberland" 87 | >}} 88 | Matt Haberland''' 89 | link = 'https://github.com/mdhaber' 90 | 91 | [[item]] 92 | type = 'card' 93 | classcard = 'text-center' 94 | body = '''{{< image 95 | src="https://avatars.githubusercontent.com/u/5363860?u=ce5c6e9388d2fd153ebf8b0bb521c928b0813608&v=4" 96 | alt="Avatar of Mridul Seth" 97 | >}} 98 | Mridul Seth''' 99 | link = 'https://github.com/MridulS' 100 | 101 | [[item]] 102 | type = 'card' 103 | classcard = 'text-center' 104 | body = '''{{< image 105 | src="https://avatars.githubusercontent.com/u/6745434?v=4" 106 | alt="Avatar of Madicken Munk" 107 | >}} 108 | Madicken Munk''' 109 | link = 'https://github.com/munkm' 110 | 111 | [[item]] 112 | type = 'card' 113 | classcard = 'text-center' 114 | body = '''{{< image 115 | src="https://avatars.githubusercontent.com/u/98330?u=22a023f8d191ba200ab13d476c83860d015cc9fe&v=4" 116 | alt="Avatar of Ralf Gommers" 117 | >}} 118 | Ralf Gommers''' 119 | link = 'https://github.com/rgommers' 120 | 121 | [[item]] 122 | type = 'card' 123 | classcard = 'text-center' 124 | body = '''{{< image 125 | src="https://avatars.githubusercontent.com/u/1268991?u=974707b96081a9705f3a239c0773320f353ee02f&v=4" 126 | alt="Avatar of Ross Barnowski" 127 | >}} 128 | Ross Barnowski''' 129 | link = 'https://github.com/rossbar' 130 | 131 | [[item]] 132 | type = 'card' 133 | classcard = 'text-center' 134 | body = '''{{< image 135 | src="https://avatars.githubusercontent.com/u/20305658?u=0305891289a16b2111326053c0f3746fb5218fa7&v=4" 136 | alt="Avatar of Sanket Verma" 137 | >}} 138 | Sanket Verma''' 139 | link = 'https://github.com/sanketverma1704' 140 | 141 | [[item]] 142 | type = 'card' 143 | classcard = 'text-center' 144 | body = '''{{< image 145 | src="https://avatars.githubusercontent.com/u/45071?u=ef2af0a29229833cd2ae0e5aeb90283881e8c1e0&v=4" 146 | alt="Avatar of Stefan van der Walt" 147 | >}} 148 | Stefan van der Walt''' 149 | link = 'https://github.com/stefanv' 150 | 151 | [[item]] 152 | type = 'card' 153 | classcard = 'text-center' 154 | body = '''{{< image 155 | src="https://avatars.githubusercontent.com/u/23188539?u=64445b52dbf3f75de8006ed4264fdd2afaed97a3&v=4" 156 | alt="Avatar of Pamphile Roy" 157 | >}} 158 | Pamphile Roy''' 159 | link = 'https://github.com/tupui' 160 | -------------------------------------------------------------------------------- /core-projects/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC Core Projects" 3 | hide_meta: true 4 | author: 5 | - "Stéfan van der Walt " 6 | - "Jarrod Millman " 7 | discussion: 8 | endorsed-by: 9 | --- 10 | 11 | ## Description 12 | 13 | Core Projects are depended upon by many other projects, 14 | and often provide basic data structures, drawing primitives, 15 | implementations of fundamental algorithms, or are metapackages 16 | that represent a particular scientific field. 17 | Due to their central position in the ecosystem, the policies, practices, and tooling 18 | used by the Core Projects are widely seen by the ecosystem 19 | and impact many other projects. 20 | The [Steering Committee](/specs/steering-committee) maintains the list of 21 | Core Projects. 22 | 23 | Core Projects endorse SPEC documents. 24 | During the endorsement stage of the SPEC process, Core Project contributors 25 | propose, discuss, and review SPEC documents with the goal of developing 26 | a coherent implementation plan suitable for all the Core Projects. 27 | Often SPECs are coauthored by contributors from several Core Projects as well 28 | as other community members (e.g., contributors to other ecosystem projects). 29 | 30 | ### Core Projects 31 | 32 | {{< project_grid files="." columns="2 3 5 6" >}} 33 | 34 | ## Implementation 35 | 36 | Core Projects are involved at all stages of the SPEC process. 37 | They work collaboratively to develop draft SPECs so that they can be endorsed. 38 | 39 | ### What are the characteristics of a Core Project? 40 | 41 | Core Projects are **widely used in scientific research**. 42 | Of course, these tools are often used for other purposes too, and the 43 | scientific Python ecosystem overlaps with the PyData ecosystem---which has a 44 | stronger focus on solving problems in industry. 45 | 46 | Core Projects are **widely used in the scientific Python ecosystem**. 47 | They are depended upon by many other projects, 48 | often providing basic data structures, drawing primitives, 49 | implementations of fundamental algorithms, or are metapackages 50 | that represent a particular scientific field. 51 | 52 | Core Projects are **developed using shared community practices**. 53 | They have a version control system, a bug tracker, a 54 | code of conduct, a contributors guide, a code review process, a public 55 | roadmap, a documented governance system, an enhancement proposal process, 56 | and regular releases on the [Python Package Index](https://pypi.org/). 57 | 58 | Core Projects are **developed in the open by their communities**. 59 | Development takes place in an online forum, and communications are public. 60 | The developers of the project do not all reside at a single institution. 61 | 62 | Core Projects are **well documented and well tested**. 63 | They document their APIs, and have inline documentation (most often in 64 | the form of the [numpydoc docstring standard](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard). 65 | They also have a high level of test coverage to ensure that a) the code 66 | does what it is intended to and b) new developers can modify the code without 67 | fear of breaking it. 68 | 69 | Core Projects are **open source**. 70 | The 3-clause (also called "modified" or "new") BSD license is by far the most common. 71 | 72 | ### How does a Core Project endorse a SPEC? 73 | 74 | See [purpose and process](/specs/purpose-and-process/#endorsing-a-spec). 75 | 76 | ### How many Core Projects should there be? 77 | 78 | This is up to the Steering Committee. 79 | But it is expected that the list of Core Projects will remain small and will not change rapidly. 80 | During the endorsement stage of the SPEC process, contributors to the Core Projects are supposed 81 | to achieve a shared agreement on how to implement the SPEC. 82 | When adding new Core Projects, the Steering Committee should ensure that doing so will 83 | only make it easier for the the Core Projects to achieve consensus. 84 | 85 | ### How do you add a project? 86 | 87 | If the Steering Committee decides to admit a new Core Project and the project agrees, then 88 | the project creates a registration file and files a pull request against the 89 | [scientific-python/specs](https://github.com/scientific-python/specs) repository. 90 | 91 | To create your project registration file, change into the 92 | `core-projects` directory and use the `register.py` script. 93 | The script will ask you a few questions and then create a new file 94 | appropriately named with a basic template for you to check and complete. 95 | 96 | ### How do you remove a project? 97 | 98 | If a Core Project no longer wishes to participate or if the Steering Committee decides to remove 99 | a Core Project, then the project registration file should be deleted, the project 100 | should be removed from the gallery above, and all occurrence of that Core Project 101 | in the `endorsed-by` field of a SPEC header should be deleted. 102 | -------------------------------------------------------------------------------- /spec-0000/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 0 — Minimum Supported Dependencies" 3 | number: 0 4 | date: 2020-12-17 5 | author: 6 | - "Andreas C. Mueller " 7 | - "Brigitta Sipőcz " 8 | - "Jarrod Millman " 9 | - "Madicken Munk " 10 | - "Matt Haberland " 11 | - "Matthias Bussonnier " 12 | - "Ralf Gommers " 13 | - "Ross Barnowski " 14 | - "Stéfan van der Walt " 15 | - "Thomas A Caswell " 16 | endorsed-by: 17 | - ipython 18 | - matplotlib 19 | - networkx 20 | - numpy 21 | - scikit-image 22 | - scipy 23 | - xarray 24 | - zarr 25 | --- 26 | 27 | ## Description 28 | 29 | This SPEC recommends that all projects across the Scientific Python ecosystem adopt a common time-based policy for dropping dependencies. From the perspective of this SPEC, the dependencies in question are core packages as well as older Python versions. 30 | 31 | All versions refer to feature releases (i.e., Python 3.8.0, NumPy 1.19.0; not Python 3.8.1, NumPy 1.19.2). 32 | 33 | Specifically, we recommend that: 34 | 35 | 1. Support for Python versions be dropped **3 years** after their initial release. 36 | 2. Support for core package dependencies be dropped **2 years** after their initial release. 37 | 38 | {{< admonition note >}} 39 | Core packages may or may not decide to provide bug fix releases during the full 2 year period after release. 40 | Therefore, projects may occasionally want to drop support for core package dependencies earlier than recommended by this SPEC. 41 | For instance, if a newer minimum version of a core package is needed by a project due to a critical bug fix, 42 | which is not backported to older versions. 43 | {{< /admonition >}} 44 | 45 | ### Core Project Endorsement 46 | 47 | 50 | 51 | Core project endorsing this SPEC means that those projects encourage all projects across the Scientific Python ecosystem 52 | to limit how long they support older Python versions and older dependency versions. 53 | A core project endorsing this SPEC does **not** imply that that project will provide bug-fix releases for two full years after a release. 54 | 55 | ### Ecosystem Adoption 56 | 57 | 60 | 61 | #### Badges 62 | 63 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 64 | {{< spec_badge number="0" title="Minimum Supported Dependencies" >}} 65 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 66 | 67 | ## Implementation 68 | 69 | ### Motivation 70 | 71 | Limiting the scope of supported dependencies is an effective way for packages to limit maintenance burden. 72 | Combinations of packages need to be tested, which impacts also on continuous integration times and infrastructure upkeep. 73 | Code itself also becomes more complicated when it has to be aware of various combinations of configurations. 74 | 75 | Adoption of this SPEC will ensure a consistent support policy across packages, and reduce the need for individual projects to divise similar policies. 76 | 77 | Ultimately, reduced maintenance burden frees up developer time, which translates into more features, bugfixes, and optimizations for users. 78 | 79 | ### Background 80 | 81 | In the past, longer support cycles were common. 82 | There were several reasons for this, including the Python 2 / 3 transition, difficulties installing packages, and users needing to use old, operating-system provided versions of Python. 83 | The situation has since improved due to improved installations via binary wheels, virtual environments becoming commonplace, and support for Python 2 being dropped. 84 | 85 | ### Support Window 86 | 87 | ```mermaid 88 | {{< include-raw "chart.md" >}} 89 | ``` 90 | 91 | ### Drop Schedule 92 | 93 | Below is an auto generated schedule with recommended dates for dropping support. We suggest that the next release in a given quarter is 94 | considered as the one removing support for a given item. 95 | 96 | You may want to delay the removal of support of an older Python version until your package fully works on the newly released Python, thus keeping the number of supported minor versions of Python the same for your package. 97 | 98 | {{< include-md "schedule.md" >}} 99 | 100 | ## Notes 101 | 102 | - This document builds on [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html), which describes several alternatives including ad hoc version support, all CPython supported versions, default version on Linux distribution, N minor versions of Python, and time window from the X.Y.1 Python release. 103 | 104 | - Code to generate support and drop schedule tables: 105 | 106 | {{< include-code "SPEC0_versions.py" "python" >}} 107 | -------------------------------------------------------------------------------- /spec-0002/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 2 — API Dispatch" 3 | number: 2 4 | date: 2021-12-16 5 | author: 6 | - "Jarrod Millman " 7 | - "Aditi Juneja " 8 | is-draft: true 9 | endorsed-by: 10 | --- 11 | 12 | ## Description 13 | 14 | 17 | 18 | This SPEC (Scientific Python Ecosystem Coordination) recommendation for API dispatching is designed to enhance interoperability and performance within the scientific Python ecosystem, leveraging the capabilities of [Python `entry_points`](https://packaging.python.org/en/latest/specifications/entry-points/). 19 | 20 | This recommendation presents a systematic approach that enables users to redirect function calls to alternative computation backends seamlessly. This flexibility allows users to take advantage of optimized implementations simply by configuring an environment variable or adding an additional keyword argument (more on this later), rather than having to learn a new API's interface, which might not be very "python-user"-friendly. Such adaptability facilitates the integration of hardware-specific optimizations, reimplementations in other programming languages (such as C or Rust), and the utilization of entirely new data structures. 21 | 22 | ### Core Project Endorsement 23 | 24 | 27 | 28 | ### Ecosystem Adoption 29 | 30 | 33 | 34 | #### Badges 35 | 36 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 37 | {{< spec_badge number="2" title="API Dispatch" >}} 38 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 39 | 40 | ## Implementation 41 | 42 | 48 | 49 | A successful prototype implementation of this SPEC is already integrated into [NetworkX](https://github.com/networkx/networkx), embodying its core principles. NetworkX has established a flexible dispatching layer capable of interfacing with multiple computational backends. Currently, this includes backends that facilitate dispatching to [CuGraph](https://github.com/rapidsai/cugraph/tree/branch-24.04/python/nx-cugraph), [GraphBLAS](https://github.com/python-graphblas/graphblas-algorithms), to parallel implementations in [nx-parallel](https://github.com/networkx/nx-parallel) and to the recently added backend [nx-pandas](https://github.com/networkx/nx-pandas). 50 | 51 | ### Overview : API dispatching in NetworkX 52 | 53 | 1. Utilization of Python `entry_point` 54 | The `entry_point` mechanism serves as a crucial component for backend discovery and integration. By registering backends through the `networkx.backends` `entry_point` in a package's metadata, developers can enable NetworkX to recognize and utilize these backends without requiring explicit imports. Example Registration: 55 | 56 | [project.entry-points."networkx.backends"] 57 | your_backend_name = "your_dispatcher_class" 58 | 59 | 2. Backend Requirements 60 | To ensure that a backend is compatible with NetworkX, it must adhere to specific structural requirements: 61 | 1. The backend must implement a class that behaves like the `nx.Graph` object, including an attribute `__networkx_backend__` that identifies the backend. 62 | 63 | 2. The backend should provide `convert_from_nx` and `convert_to_nx` methods to facilitate the conversion between NetworkX graphs and the backend's graph representation. 64 | 65 | 3. Testing the backend 66 | To ensure that the backend complies with the main NetworkX's API, developers are encouraged to run the NetworkX test suite on their custom backend. Testing Setup: 67 | 68 | NETWORKX_TEST_BACKEND= 69 | pytest --pyargs networkx 70 | 71 | 4. Currently, the following ways exist in NetworkX to dispatch a function call to a backend: 72 | 1. Type-based dispatching 73 | ```py 74 | H = nx_parallel.ParallelGraph(G) 75 | nx.betweenness_centrality(H) 76 | ``` 77 | 2. Using a `backend` kwarg 78 | ```py 79 | nx.betweenness_centrality(G, backend=”parallel”) 80 | ``` 81 | 3. Environment variable 82 | ```sh 83 | export NETWORKX_AUTOMATIC_BACKENDS=parallel && python nx_code.py 84 | ``` 85 | 4. [WIP] Specifying backend as a config parameter (ref. [PR#7485](https://github.com/networkx/networkx/pull/7485)) 86 | ```py 87 | with nx.config(backend=”parallel”): 88 | nx.betweenness_centrality(G) 89 | ``` 90 | 91 | For more, read the [NetworkX's Backend and Config docs](https://networkx.org/documentation/latest/reference/backends.html)! 92 | 93 | ### Interesting discussions happening in: 94 | 95 | - [`spatch`](https://github.com/scientific-python/spatch) repository 96 | - ["Requirements and discussion of a type dispatcher for the ecosystem"](https://discuss.scientific-python.org/t/requirements-and-discussion-of-a-type-dispatcher-for-the-ecosystem/157) discussion 97 | - [Scientific Python Discourse - SPEC 2](https://discuss.scientific-python.org/t/spec-2-api-dispatch/173) 98 | 99 | ### Projects developing a Python `entry_points` based backend dispatching: 100 | 101 | - networkx : [Backend and Config docs](https://networkx.org/documentation/latest/reference/backends.html) 102 | - scikit-image : [PR#7466](https://github.com/scikit-image/scikit-image/pull/7466) 103 | - scikit-learn : [PR#25535](https://github.com/scikit-learn/scikit-learn/pull/25535) 104 | 105 | ## Notes 106 | 107 | 111 | -------------------------------------------------------------------------------- /steering-committee/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC Steering Committee" 3 | date: 2021-01-06 4 | author: 5 | - "Jarrod Millman " 6 | - "Stéfan van der Walt " 7 | --- 8 | 9 | ## Description 10 | 11 | The SPEC process is managed by the Steering Committee. 12 | The Steering Committee represents the interests of the ecosystem and the community. 13 | The Steering Committee also represent the interests of the 14 | [Core Projects](/specs/core-projects) 15 | and is composed partially of individuals who are active Core Project contributors. 16 | In particular, the Steering Committee members 17 | 18 | - monitor the 19 | [SPECs discussion forum](https://discuss.scientific-python.org/c/specs/6), 20 | - determine which proposed SPECs are accepted as described in the [SPEC 21 | Purpose and Process](/specs/purpose-and-process), 22 | - approve changes to the SPEC process including to the 23 | [SPEC Purpose and Process](/specs/purpose-and-process), 24 | [SPEC Steering Committee](/specs/steering-committee), and 25 | [SPEC Core Projects](/specs/core-projects), as well as 26 | - serve as a communication channel to and from projects they contribute to as 27 | well as the larger ecosystem. 28 | 29 | The SPEC Committee is led by the SPEC Steering Committee Chair. 30 | The SPEC Steering Committee Chair is a designated member of the Steering Committee, selected by the Steering Committee. 31 | The Chair serves at the discretion of the Steering Committee and may be replaced by the committee at any time. 32 | The primary responsibility of the Chair is to organize and facilitate the SPEC Planning Meetings. 33 | In addition to coordinating these meetings, the Chair may assist in managing meeting agendas and ensuring effective communication within the committee. 34 | The Chair is also responsible for ensuring the Steering Committee is familiar with and following the SPEC Process. 35 | 36 | ### SPEC Steering Committee Chair 37 | 38 | {{< grid file="spec-steering-committee-chair.toml" columns="2 3 4 5" />}} 39 | 40 | ### SPEC Steering Committee 41 | 42 | {{< grid file="spec-steering-committee.toml" columns="2 3 4 5" />}} 43 | 44 | ### Emeritus SPEC Steering Committee 45 | 46 | {{< grid file="emeritus-spec-steering-committee.toml" columns="2 3 4 5" />}} 47 | 48 | ## SPEC Planning Meetings 49 | 50 | SPEC Planning Meetings are regular gatherings organized to discuss ongoing and upcoming SPEC proposals, procedural updates, and any other matters relevant to the Steering Committee or the broader community. 51 | The Chair is responsible for scheduling and coordinating these meetings. 52 | Information about upcoming SPEC Planning Meetings, including dates and details for joining, can be found on the [SPEC Steering Committee](https://scientific-python.org/calendars/specs.ics) calendar hosted at the [Scientific Python Calendars](https://scientific-python.org/calendars/). 53 | 54 | The meetings are open to all community members. 55 | [Meeting notes](https://hackmd.io/@stefanv/SPEC-meetings/edit) and outcomes are documented and shared via public community channels as appropriate. 56 | 57 | ## Implementation 58 | 59 | Public communication takes place in the 60 | [`specs` GitHub repository](https://github.com/scientific-python/specs/) 61 | and the [SPECs discussion forum](https://discuss.scientific-python.org/c/specs/6). 62 | Private communication within the Steering Committee takes place on 63 | [Steering Committee private mailing list](mailto:spec-steering-committee@discuss.scientific-python.org). 64 | Steering Committee members are expected to be aware of conversations on this list to lend validity 65 | to consensus-seeking and voting. 66 | 67 | ### How are SPECs accepted? 68 | 69 | 70 | 71 | Also refer to [SPEC Purpose and Process: New 72 | Proposals](/specs/purpose-and-process/#new-spec-proposals), a summary 73 | of steps for SPEC authors. 74 | 75 | To accept a SPEC requires two members of the Steering Committee to 76 | approve and no members objecting. 77 | 78 | Verify that: 79 | 80 | 1. The SPEC has two authors from two projects; and 81 | 2. that the idea is widely applicable to the ecosystem (i.e., it makes sense to 82 | write this up as a SPEC). 83 | 84 | Assign the SPEC a number, and ask the authors to submit a draft PR 85 | with a preliminary write-up. 86 | 87 | The role of the Steering Committee is mainly to ensure that SPEC 88 | proposals are appropriate, so objections should be rare. 89 | 90 | ### How does the SPEC Committee make decisions? 91 | 92 | The Steering Committee makes decisions about changing the SPEC process documents 93 | ([SPEC Purpose and Process](/specs/purpose-and-process), 94 | [SPEC Steering Committee](/specs/steering-committee), and 95 | [SPEC Core Projects](/specs/core-projects)) 96 | through group consensus and, in the very rare instance 97 | where no consensus can be reached, by two-thirds majority vote of those 98 | available to cast a vote within ten days. 99 | 100 | 107 | 108 | ### Who should be a member? 109 | 110 | Members of the Steering Committee should be active in the scientific Python ecosystem and 111 | should have a demonstrated interest in the Core Projects and the SPEC process. 112 | Examples of demonstrated interest include submitting SPECs, engaging in SPEC 113 | discussions, reviewing SPEC pull requests, or advocating for wider SPEC participation. 114 | Members of the steering committee do not have to belong to a core project. 115 | 116 | ### How many members should there be? 117 | 118 | This is up to the Steering Committee. 119 | However, if the Steering Committee is unable to quickly handle new SPEC proposals and new ideas arising 120 | in the discussions aren't addressed in a timely manner, the Steering Committee should try to 121 | recruit new members. 122 | 123 | ### How do you add a member? 124 | 125 | If the Steering Committee decides to admit a new member and that person agrees, 126 | then they should be added to the 127 | (1) the Steering Committee member listing above, 128 | (2) the [Steering Committee Team](https://github.com/orgs/scientific-python/teams/spec-steering-committee/members), and 129 | (3) the [Steering Committee Discourse Group](https://discuss.scientific-python.org/g/spec-steering-committee). 130 | 131 | ### How do you remove a member? 132 | 133 | If a member wishes to resign or if the Steering Committee decides to remove a member, 134 | then they should 135 | (1) be moved to the list of Emeritus Steering Committee members (below the list of active members), 136 | (2) be removed from the 137 | [Steering Committee Team](https://github.com/orgs/scientific-python/teams/spec-steering-committee/members), and 138 | (3) be removed from the 139 | [Steering Committee Discourse Group](https://discuss.scientific-python.org/g/spec-steering-committee). 140 | -------------------------------------------------------------------------------- /spec-0006/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 6 — Keys to the Castle" 3 | number: 6 4 | date: 2022-12-19 5 | author: 6 | - "Stéfan van der Walt " 7 | - "Jarrod Millman " 8 | - "Pamphile Roy " 9 | - "Lars Grüter " 10 | endorsed-by: 11 | - ipython 12 | - networkx 13 | - numpy 14 | - scikit-image 15 | - scikit-learn 16 | --- 17 | 18 | ## Description 19 | 20 | 26 | 27 | Projects engage with restricted resources all the time. 28 | Examples include access to add & remove team members, grant commit rights, or make uploads to certain hosts. 29 | 30 | **Documenting resources** is critical to ensuring uninterrupted operations. 31 | It is important that team members know _who_ have access to resources, and how to _gain access_ to resources. 32 | For example, if updated project documentation needs to be uploaded to a remote server via SSH, who are the team members that can do that, and by which process can a release manager request access? 33 | 34 | Furthermore, project developers sometimes have to **share secrets**, such as server passwords and social media logins. 35 | Typically, such secrets are distributed among those who need them, without any centralized system for tracking the secrets or who has access to them. 36 | When a server needs to be accessed, there is often a scramble to find someone with credentials. 37 | 38 | This SPEC discusses the requirements for a system to distribute secrets, provides an example implementation, and lists suitable hosted services. 39 | 40 | It should be noted that, in many cases, secrets & passwords can be avoided. 41 | Most online services (GitHub, PyPI, etc.), have permissioning systems through which developers can be given the required access. 42 | Access to servers can be given through SSH keys, which each developer has to safeguard. 43 | This document deals with the situation in which that is not possible, and secrets have to be shared. 44 | 45 | ### Principles 46 | 47 | 1. Restricted project resources must be documented. 48 | 49 | Examples include servers that host services or web pages, and the processes for adding/removing project members on GitHub, chat, mailing lists, etc. 50 | 51 | 2. Assign the lowest privileges needed for a developer to do their work meaningfully. 52 | 53 | Review permissions regularly (say, every year) to maintain minimal permissions. 54 | 55 | _Tokens_ are a typical example of a stored secret that should be scoped (minimal necessary permissions), and set to expire after a reasonable time period (a year, typically). 56 | Instructions for rotating and revoking such tokens must be documented. 57 | 58 | 3. Project assets should, wherever possible, be accessible by at least two maintainers. 59 | This ensures access to assets, even when a key team member leaves the project or becomes indisposed. 60 | 61 | 4. A system for distributing project secrets must have the following properties: 62 | - Secrets are stored encrypted in a central (remote) location. 63 | - It must be possible to grant access to the secrets to a select group of team members. 64 | - It must be possible to revoke future access to the secrets.[^future-access] 65 | - The system must not rely on closed source or commercial encryption facilities, that 66 | can later disappear or be made unavailable, although such a solution can be considered if it allows for all data to be exported. 67 | 68 | Whichever system is chosen, its user interface should match the capabilities of the intended users. 69 | This reduces the risk of passwords being copied out to less secure mechanisms such as sticky notes or text files. 70 | If the target audience is not used to GPG or the command prompt, for example, the `pass` implementation below may not work, and an alternative like 1password or bitwarden should be considered. 71 | 72 | [^future-access]: Revoking access to a service implies both (a) revoking access to secrets and (b) re-generating those secrets, since the actor could have copied them. 73 | 74 | ### Core Project Endorsement 75 | 76 | 79 | 80 | ### Ecosystem Adoption 81 | 82 | 85 | 86 | #### Badges 87 | 88 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 89 | {{< spec_badge number="6" title="Keys to the Castle" >}} 90 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 91 | 92 | ## Implementation 93 | 94 | ### Password storage: hosted 95 | 96 | The following hosted solutions conform to the principles in (2) above. 97 | 98 | Self-hosted solutions: 99 | 100 | - [vaultwarden](https://github.com/dani-garcia/vaultwarden); open source 101 | 102 | Paid solutions: 103 | 104 | - [bitwarden](https://bitwarden.com/) provides a non-profit discount of 25%; hosted open source 105 | 106 | Sponsored solutions: 107 | 108 | - [1password](https://github.com/1Password/1password-teams-open-source); closed source 109 | 110 | #### Password storage: offline 111 | 112 | [Pass](https://www.passwordstore.org/) is the standard unix password manager, which stores passwords as encrypted files on disk. 113 | [password vault](https://github.com/scientific-python/vault-template) is an example implementation that satisfies the principles listed above. 114 | The secrets are stored, encrypted, in a public Git repository. 115 | The vault uses [gopass](https://github.com/gopasspw/gopass), a more user friendly implementation of pass, to manage access via GPG keys. 116 | Each secret is encrypted using the public keys of all developers that should have access. 117 | If a developer's access is removed, the vault is re-encrypted so that that developer cannot read future copies of the repository (but secrets are considered compromised and must, thus, be rotated). 118 | 119 | ### Other common scenarios 120 | 121 | - **Publishing packages**: PyPi provides a [trusted publisher](https://docs.pypi.org/trusted-publishers/using-a-publisher/) mechanism for avoiding passwords 122 | 123 | ### Other security recommendations 124 | 125 | - **2FA**: Developers must use two-factor authentication for service logins. 126 | This reduces the risk of account takeovers and subsequent fraudulent software releases. 127 | - **Passwords** must be generated by a password manager. 128 | This ensures that they are of sufficient length and complexity. 129 | - **SSH keys** must have a password. Ed25519 is the current recommended key type, and can be generated with `ssh-keygen -t ed25519`. 130 | 131 | ## Notes 132 | 133 | See [gopass's security goals](https://github.com/gopasspw/gopass/blob/master/docs/security.md#security-goals). 134 | 135 | 139 | -------------------------------------------------------------------------------- /spec-0000/SPEC0_versions.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import collections 3 | from datetime import datetime, timedelta 4 | 5 | import pandas as pd 6 | from packaging.version import Version, InvalidVersion 7 | 8 | 9 | py_releases = { 10 | "3.8": "Oct 14, 2019", 11 | "3.9": "Oct 5, 2020", 12 | "3.10": "Oct 4, 2021", 13 | "3.11": "Oct 24, 2022", 14 | "3.12": "Oct 2, 2023", 15 | "3.13": "Oct 7, 2024", 16 | "3.14": "Oct 7, 2025", 17 | } 18 | core_packages = [ 19 | # Path(x).stem for x in glob("../core-projects/*.md") if "_index" not in x 20 | "numpy", 21 | "scipy", 22 | "matplotlib", 23 | "pandas", 24 | "scikit-image", 25 | "networkx", 26 | "scikit-learn", 27 | "xarray", 28 | "ipython", 29 | "zarr", 30 | ] 31 | plus36 = timedelta(days=int(365 * 3)) 32 | plus24 = timedelta(days=int(365 * 2)) 33 | 34 | # Release data 35 | 36 | # put cutoff 3 quarters ago – we do not use "just" -9 month, 37 | # to avoid the content of the quarter to change depending on when we generate this 38 | # file during the current quarter. 39 | 40 | current_date = pd.Timestamp.now() 41 | current_quarter_start = pd.Timestamp( 42 | current_date.year, (current_date.quarter - 1) * 3 + 1, 1 43 | ) 44 | cutoff = current_quarter_start - pd.DateOffset(months=9) 45 | 46 | 47 | def get_release_dates(package, support_time=plus24): 48 | releases = {} 49 | 50 | print(f"Querying pypi.org for {package} versions...", end="", flush=True) 51 | response = requests.get( 52 | f"https://pypi.org/simple/{package}", 53 | headers={"Accept": "application/vnd.pypi.simple.v1+json"}, 54 | ).json() 55 | print("OK") 56 | 57 | file_date = collections.defaultdict(list) 58 | for f in response["files"]: 59 | ver = f["filename"].split("-")[1] 60 | try: 61 | version = Version(ver) 62 | except InvalidVersion as e: 63 | print(f"Error: '{ver}' is an invalid version for '{package}'. Reason: {e}") 64 | continue 65 | 66 | if version.is_prerelease or version.micro != 0: 67 | continue 68 | 69 | release_date = None 70 | for format in ["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"]: 71 | try: 72 | release_date = datetime.strptime(f["upload-time"], format) 73 | except ValueError as e: 74 | print(f"Error parsing invalid date: {e}") 75 | 76 | if not release_date: 77 | continue 78 | 79 | file_date[version].append(release_date) 80 | 81 | release_date = {v: min(file_date[v]) for v in file_date} 82 | 83 | for ver, release_date in sorted(release_date.items()): 84 | drop_date = release_date + support_time 85 | if drop_date >= cutoff: 86 | releases[ver] = { 87 | "release_date": release_date, 88 | "drop_date": drop_date, 89 | } 90 | 91 | return releases 92 | 93 | 94 | package_releases = { 95 | "python": { 96 | version: { 97 | "release_date": datetime.strptime(release_date, "%b %d, %Y"), 98 | "drop_date": datetime.strptime(release_date, "%b %d, %Y") + plus36, 99 | } 100 | for version, release_date in py_releases.items() 101 | } 102 | } 103 | 104 | package_releases |= {package: get_release_dates(package) for package in core_packages} 105 | 106 | # filter all items whose drop_date are in the past 107 | package_releases = { 108 | package: { 109 | version: dates 110 | for version, dates in releases.items() 111 | if dates["drop_date"] > cutoff 112 | } 113 | for package, releases in package_releases.items() 114 | } 115 | 116 | 117 | # Save Gantt chart 118 | 119 | print("Saving Mermaid chart to chart.md") 120 | with open("chart.md", "w") as fh: 121 | fh.write( 122 | """gantt 123 | dateFormat YYYY-MM-DD 124 | axisFormat %m / %Y 125 | title Support Window""" 126 | ) 127 | 128 | for name, releases in package_releases.items(): 129 | fh.write(f"\n\nsection {name}") 130 | for version, dates in releases.items(): 131 | fh.write( 132 | f"\n{version} : {dates['release_date'].strftime('%Y-%m-%d')},{dates['drop_date'].strftime('%Y-%m-%d')}" 133 | ) 134 | fh.write("\n") 135 | 136 | # Print drop schedule 137 | 138 | data = [] 139 | for k, versions in package_releases.items(): 140 | for v, dates in versions.items(): 141 | data.append( 142 | ( 143 | k, 144 | v, 145 | pd.to_datetime(dates["release_date"]), 146 | pd.to_datetime(dates["drop_date"]), 147 | ) 148 | ) 149 | 150 | df = pd.DataFrame(data, columns=["package", "version", "release", "drop"]) 151 | 152 | df["quarter"] = df["drop"].dt.to_period("Q") 153 | 154 | dq = df.set_index(["quarter", "package"]).sort_index() 155 | 156 | 157 | print("Saving drop schedule to schedule.md") 158 | 159 | 160 | def pad_table(table): 161 | rows = [[el.strip() for el in row.split("|")] for row in table] 162 | col_widths = [max(map(len, column)) for column in zip(*rows)] 163 | rows[1] = [ 164 | el if el != "----" else "-" * col_widths[i] for i, el in enumerate(rows[1]) 165 | ] 166 | padded_table = [] 167 | for row in rows: 168 | line = "" 169 | for entry, width in zip(row, col_widths): 170 | if not width: 171 | continue 172 | line += f"| {str.ljust(entry, width)} " 173 | line += "|" 174 | padded_table.append(line) 175 | 176 | return padded_table 177 | 178 | 179 | def make_table(sub): 180 | table = [] 181 | table.append("| | | |") 182 | table.append("|----|----|----|") 183 | for package in sorted(set(sub.index.get_level_values(0))): 184 | vers = sub.loc[[package]]["version"] 185 | minv, maxv = min(vers), max(vers) 186 | rels = sub.loc[[package]]["release"] 187 | rel_min, rel_max = min(rels), max(rels) 188 | version_range = str(minv) if minv == maxv else f"{minv} to {maxv}" 189 | rel_range = ( 190 | str(rel_min.strftime("%b %Y")) 191 | if rel_min == rel_max 192 | else f"{rel_min.strftime('%b %Y')} and {rel_max.strftime('%b %Y')}" 193 | ) 194 | table.append(f"|{package:<15}|{version_range:<19}|released {rel_range}|") 195 | 196 | return pad_table(table) 197 | 198 | 199 | def make_quarter(quarter, dq): 200 | table = ["#### " + str(quarter).replace("Q", " - Quarter ") + ":\n"] 201 | table.append("###### Recommend drop support for:\n") 202 | sub = dq.loc[quarter] 203 | table.extend(make_table(sub)) 204 | return "\n".join(table) 205 | 206 | 207 | with open("schedule.md", "w") as fh: 208 | # we collect package 6 month in the past, and drop the first quarter 209 | # as we might have filtered some of the packages out depending on 210 | # when we ran the script. 211 | tb = [] 212 | for quarter in list(sorted(set(dq.index.get_level_values(0))))[1:]: 213 | tb.append(make_quarter(quarter, dq)) 214 | 215 | fh.write("\n\n".join(tb)) 216 | fh.write("\n") 217 | -------------------------------------------------------------------------------- /spec-0003/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 3 — Accessibility" 3 | number: 3 4 | date: 2022-02-14 5 | author: 6 | - "Juanita Gomez " 7 | - "Sarah Kaiser " 8 | is-draft: true 9 | endorsed-by: 10 | --- 11 | 12 | ## Description 13 | 14 | Technology is a great tool to make the world accessible to the full range of human experience, which includes [those with disabilities](https://www.cdc.gov/ncbddd/disabilityandhealth/infographic-disability-impacts-all.html). The reach of accessibility guidelines extends beyond the scientific Python ecosystem including the [Web Content Accessibility Guidelines (W3C)](https://www.w3.org/TR/WCAG/), a comprehensive set of international standards designed to make web content more accessible. 15 | 16 | The primary objective of this SPEC (Scientific Python Accessibility) is to provide fundamental recommendations for the Scientific Python communities and their projects. These recommendations aim to ensure accessibility and inclusivity for individuals with disabilities, particularly regarding web-based content and tools. 17 | 18 | As active members of the scientific Python and open-source software (OSS) communities, we are dedicated to leveraging technology to create an inclusive environment that embraces everyone. 19 | 20 | It is important to note that accessibility is an ongoing journey, and you need not be overwhelmed by the many recommendations outlined in the provided resources. Taking an incremental approach allows for continuous improvement, ensuring that each enhancement makes technology more accessible and user-friendly. 21 | 22 | ### Core Project Endorsement 23 | 24 | 27 | 28 | ### Ecosystem Adoption 29 | 30 | 33 | 34 | #### Badges 35 | 36 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 37 | {{< spec_badge number="3" title="Accessibility" >}} 38 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 39 | 40 | ## Implementation 41 | 42 | ### 1. Alt text 43 | 44 | All images, figures, and media elements on the web should be provided with meaningful [alt text](https://www.w3.org/WAI/test-evaluate/preliminary/#images). Alt text is meant to give screen readers something to read aloud to visually impaired users. 45 | 46 | ### 2. Color 47 | 48 | - **Contrast levels** between content and the background should be enough for anyone with low vision impairments and color deficiencies to be able to read. We list here the recommended values for contrast depending on the size of the text or element on the website. Still, we recommend using an [automated tool](https://hackmd.io/VMHHxV7dR0mwuNuSYci7xw?both) to measure these values for your website. 49 | - Normal text: [Level AA](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum) compliance requires a contrast ratio of 4.5:1. 50 | - Large text: Level AA compliance requires a contrast ratio of 3:1. 51 | - Icons: Level AA compliance requires a contrast ratio of 3.0:1. 52 | - Make sure your website/application is **colorblind friendly**. There are several ways to do this: 53 | - Convey information in some other way (not using colors). 54 | - Use [color palettes that are colorblind friendly](https://jfly.uni-koeln.de/color/#pallet), meaning that they avoid certain combinations of colors, and try to maximize the contrast between colors for a variety of common color blind conditions. 55 | 56 | ### 3. Navigation consistency (tab stops) 57 | 58 | Users must be able to interact with a software application using only a keyboard. For this, you should have properly designed [tab stops](https://accessibilityinsights.io/docs/windows/reference/tabstops/). A tab stop is where the cursor stops after the Tab key is pressed. 59 | 60 | - Provide a **visible focus** indicator on the interactive element with input focus, usually a border or other visible changes. 61 | - Make all **interactive elements focusable**, allowing users to tab to them unless disabled. 62 | - Maintain a **consistent tab order** that matches the visual hierarchy, including elements that appear in multiple views. 63 | 64 | ### 4. Mobile friendly 65 | 66 | - Implement a **responsive layout** for your website to ensure it adapts well to different screen sizes and devices. 67 | - **Optimize the speed** of your website by minimizing file sizes, leveraging caching, and optimizing images. 68 | - **Avoid using pop-ups** on mobile devices as they can be intrusive and disrupt the user experience. 69 | - Use a **large and readable font size** at least 16 pixels or larger for easy legibility on mobile screens. 70 | - **Simplify your website design**, removing clutter and focusing on essential elements for intuitive mobile navigation. 71 | 72 | ## Tools and Automation to help 73 | 74 | ### General tools 75 | 76 | - [Accessibility Insights](https://accessibilityinsights.io/): Automated browser-based tool that will check all of the above recommendations. 77 | - [Github Action testing](https://github.com/marketplace/actions/web-accessibility-evaluation) based on A11y recommendations. 78 | - [Axe](https://github.com/dequelabs/axe-core): Open source library (and other paid tooling) that can automate front-end testing. 79 | 80 | ### Color testing 81 | 82 | - [Color blind accessible pallets in Bokeh.](https://docs.bokeh.org/en/latest/docs/reference/palettes.html#large-palettes) 83 | - [Microsoft guide](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/accessibility/test-color-blindness) about color blind checking via browser tools. 84 | 85 | ## Need more? 86 | 87 | While the recommendations provided here cover important aspects of accessibility, they are not comprehensive. It's essential to explore additional resources and practices to further improve accessibility in your website or applications. Here are some additional resources to consider for enhancing accessibility: 88 | 89 | - The [Web Content Accessibility Guidelines (W3C)](https://www.w3.org/TR/WCAG/) provide a comprehensive set of international standards for enhancing web content accessibility. 90 | - The [comprehensive checklist](https://www.a11yproject.com/checklist/) published by the A11y organization offers a user-friendly description of the W3C's accessibility standards. 91 | - The [Numfocus Discover Cookbook](https://discover-cookbook.numfocus.org/intro.html) is a valuable resource for understanding event/meeting accessibility and implementing inclusive practices. 92 | - The [Accessibility Insights YouTube channel](https://www.youtube.com/@AccessibilityInsights) provides tutorials on using Accessibility Insights tools to test and improve website accessibility. 93 | - The articles available on the The [axxeslab Articles website](https://axesslab.com/articles/) offer helpful information about accessibility and testing. 94 | 95 | ## Submit your feedback 96 | 97 | We value your feedback and are committed to continuously improving accessibility. If you have any concerns or suggestions regarding accessibility that you believe we may have overlooked, we encourage you to share them with us. Your input is essential to our learning and growth. Please submit your issues and specific accessibility requirements to our [SPECS repository](https://github.com/scientific-python/specs) so that we can address them effectively. 98 | -------------------------------------------------------------------------------- /spec-0000/schedule.md: -------------------------------------------------------------------------------- 1 | #### 2025 - Quarter 2: 2 | 3 | ###### Recommend drop support for: 4 | 5 | | | | | 6 | | ------------ | -------------------- | ------------------------------ | 7 | | ipython | 8.13.0 to 8.14.0 | released Apr 2023 and Jun 2023 | 8 | | networkx | 3.1 | released Apr 2023 | 9 | | numpy | 1.25.0 | released Jun 2023 | 10 | | pandas | 2.0.0 | released Apr 2023 | 11 | | scikit-image | 0.21.0 | released Jun 2023 | 12 | | scikit-learn | 1.3.0 | released Jun 2023 | 13 | | scipy | 1.11.0 | released Jun 2023 | 14 | | xarray | 2023.4.0 to 2023.6.0 | released Apr 2023 and Jun 2023 | 15 | | zarr | 2.15.0 | released Jun 2023 | 16 | 17 | #### 2025 - Quarter 3: 18 | 19 | ###### Recommend drop support for: 20 | 21 | | | | | 22 | | ---------- | -------------------- | ------------------------------ | 23 | | ipython | 8.15.0 to 8.16.0 | released Sep 2023 and Sep 2023 | 24 | | matplotlib | 3.8.0 | released Sep 2023 | 25 | | numpy | 1.26.0 | released Sep 2023 | 26 | | pandas | 2.1.0 | released Aug 2023 | 27 | | xarray | 2023.7.0 to 2023.9.0 | released Jul 2023 and Sep 2023 | 28 | | zarr | 2.16.0 | released Jul 2023 | 29 | 30 | #### 2025 - Quarter 4: 31 | 32 | ###### Recommend drop support for: 33 | 34 | | | | | 35 | | ------------ | ---------------------- | ------------------------------ | 36 | | ipython | 8.17.0 to 8.19.0 | released Oct 2023 and Dec 2023 | 37 | | networkx | 3.2 | released Oct 2023 | 38 | | python | 3.11 | released Oct 2022 | 39 | | scikit-image | 0.22.0 | released Oct 2023 | 40 | | xarray | 2023.10.0 to 2023.12.0 | released Oct 2023 and Dec 2023 | 41 | 42 | #### 2026 - Quarter 1: 43 | 44 | ###### Recommend drop support for: 45 | 46 | | | | | 47 | | ------------ | -------------------- | ------------------------------ | 48 | | ipython | 8.20.0 to 8.23.0 | released Jan 2024 and Mar 2024 | 49 | | pandas | 2.2.0 | released Jan 2024 | 50 | | scikit-learn | 1.4.0 | released Jan 2024 | 51 | | scipy | 1.12.0 | released Jan 2024 | 52 | | xarray | 2024.1.0 to 2024.3.0 | released Jan 2024 and Mar 2024 | 53 | | zarr | 2.17.0 | released Feb 2024 | 54 | 55 | #### 2026 - Quarter 2: 56 | 57 | ###### Recommend drop support for: 58 | 59 | | | | | 60 | | ------------ | -------------------- | ------------------------------ | 61 | | ipython | 8.24.0 to 8.26.0 | released Apr 2024 and Jun 2024 | 62 | | matplotlib | 3.9.0 | released May 2024 | 63 | | networkx | 3.3 | released Apr 2024 | 64 | | numpy | 2.0.0 | released Jun 2024 | 65 | | scikit-image | 0.23.0 to 0.24.0 | released Apr 2024 and Jun 2024 | 66 | | scikit-learn | 1.5.0 | released May 2024 | 67 | | scipy | 1.13.0 to 1.14.0 | released Apr 2024 and Jun 2024 | 68 | | xarray | 2024.5.0 to 2024.6.0 | released May 2024 and Jun 2024 | 69 | | zarr | 2.18.0 | released May 2024 | 70 | 71 | #### 2026 - Quarter 3: 72 | 73 | ###### Recommend drop support for: 74 | 75 | | | | | 76 | | ------- | -------------------- | ------------------------------ | 77 | | ipython | 8.27.0 | released Aug 2024 | 78 | | numpy | 2.1.0 | released Aug 2024 | 79 | | xarray | 2024.7.0 to 2024.9.0 | released Jul 2024 and Sep 2024 | 80 | 81 | #### 2026 - Quarter 4: 82 | 83 | ###### Recommend drop support for: 84 | 85 | | | | | 86 | | ------------ | ---------------------- | ------------------------------ | 87 | | ipython | 8.28.0 to 8.31.0 | released Oct 2024 and Dec 2024 | 88 | | matplotlib | 3.10.0 | released Dec 2024 | 89 | | networkx | 3.4 | released Oct 2024 | 90 | | numpy | 2.2.0 | released Dec 2024 | 91 | | python | 3.12 | released Oct 2023 | 92 | | scikit-image | 0.25.0 | released Dec 2024 | 93 | | scikit-learn | 1.6.0 | released Dec 2024 | 94 | | xarray | 2024.10.0 to 2024.11.0 | released Oct 2024 and Nov 2024 | 95 | 96 | #### 2027 - Quarter 1: 97 | 98 | ###### Recommend drop support for: 99 | 100 | | | | | 101 | | ------- | -------------------- | ------------------------------ | 102 | | ipython | 8.32.0 to 9.0.0 | released Jan 2025 and Mar 2025 | 103 | | scipy | 1.15.0 | released Jan 2025 | 104 | | xarray | 2025.1.0 to 2025.3.0 | released Jan 2025 and Mar 2025 | 105 | | zarr | 3.0.0 | released Jan 2025 | 106 | 107 | #### 2027 - Quarter 2: 108 | 109 | ###### Recommend drop support for: 110 | 111 | | | | | 112 | | ------------ | -------------------- | ------------------------------ | 113 | | ipython | 8.35.0 to 9.3.0 | released Apr 2025 and May 2025 | 114 | | networkx | 3.5 | released May 2025 | 115 | | numpy | 2.3.0 | released Jun 2025 | 116 | | pandas | 2.3.0 | released Jun 2025 | 117 | | scikit-learn | 1.7.0 | released Jun 2025 | 118 | | scipy | 1.16.0 | released Jun 2025 | 119 | | xarray | 2025.4.0 to 2025.6.0 | released Apr 2025 and Jun 2025 | 120 | 121 | #### 2027 - Quarter 3: 122 | 123 | ###### Recommend drop support for: 124 | 125 | | | | | 126 | | ------- | -------------------- | ------------------------------ | 127 | | ipython | 9.4.0 to 9.6.0 | released Jul 2025 and Sep 2025 | 128 | | xarray | 2025.7.0 to 2025.9.0 | released Jul 2025 and Sep 2025 | 129 | | zarr | 3.1.0 | released Jul 2025 | 130 | 131 | #### 2027 - Quarter 4: 132 | 133 | ###### Recommend drop support for: 134 | 135 | | | | | 136 | | -------- | ---------------------- | ------------------------------ | 137 | | ipython | 9.7.0 | released Nov 2025 | 138 | | networkx | 3.6 | released Nov 2025 | 139 | | python | 3.13 | released Oct 2024 | 140 | | xarray | 2025.10.0 to 2025.11.0 | released Oct 2025 and Nov 2025 | 141 | 142 | #### 2028 - Quarter 4: 143 | 144 | ###### Recommend drop support for: 145 | 146 | | | | | 147 | | ------ | ---- | ----------------- | 148 | | python | 3.14 | released Oct 2025 | 149 | -------------------------------------------------------------------------------- /spec-0004/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 4 — Using and Creating Nightly Wheels" 3 | number: 4 4 | date: 2022-09-05 5 | author: 6 | - "Brigitta Sipőcz " 7 | - "Jarrod Millman " 8 | - "Matthew Feickert " 9 | - "Matthias Bussonnier " 10 | - "Mridul Seth " 11 | - "Olivier Grisel " 12 | - "Tim Head " 13 | endorsed-by: 14 | - ipython 15 | - matplotlib 16 | - networkx 17 | - numpy 18 | - scikit-image 19 | - scikit-learn 20 | - scipy 21 | - xarray 22 | - zarr 23 | --- 24 | 25 | ## Description 26 | 27 | This SPEC describes how to test against nightly wheels of several widely used 28 | projects and how to create nightly wheels for your project. The document uses the word 29 | _nightly_ to refer to some semi regular interval, like daily, weekly, or every three days. 30 | 31 | Regularly running your project's tests while using the nightly version of your 32 | dependencies allows you to spot problems caused by upstream changes before a new release 33 | is made. This way potential issues can be resolved before they find their way 34 | into a release, at which point it becomes much harder to change or revert 35 | something. 36 | 37 | Regularly creating nightly wheels for your project allows projects that depend on 38 | you to give feedback about upcoming changes. As with testing against nightlies of 39 | your dependencies this gives your dependents a chance to report problems before they 40 | find their way into a release. 41 | 42 | ### Core Project Endorsement 43 | 44 | 47 | 48 | ### Ecosystem Adoption 49 | 50 | 53 | 54 | #### Badges 55 | 56 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 57 | {{< spec_badge number="4" title="Using and Creating Nightly Wheels" >}} 58 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 59 | 60 | ## Implementation 61 | 62 | This section outlines how to implement using and building nightly wheels. We assume your 63 | project already has some amount of CI infrastructure and that you will have to fit this 64 | in with the existing setup. In the notes section we link to the projects who have implemented 65 | this in their setup to give you examples of complete setups. 66 | 67 | ### Test with Nightly Wheels 68 | 69 | We recommend that projects add a weekly cron job to run their tests using nightly versions 70 | of their dependencies. The cron job should automatically open an issue on your repository 71 | when it encounters an error. 72 | 73 | If you spot a problem please investigate if this is due to a known deprecation or 74 | bug fix. If you think it is neither, please report it to the relevant upstream project. 75 | 76 | To install the nightly version of your dependencies check which of them are available 77 | at https://anaconda.org/scientific-python-nightly-wheels/. For example to install the NumPy and SciPy nightlies use: 78 | 79 | ``` 80 | python -m pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy scipy 81 | ``` 82 | 83 | Complete examples of how projects implement this in their CI setup are linked in the Notes section. 84 | 85 | ### Build Nightly Wheels 86 | 87 | There are a few steps to implementing this for your project: 88 | 89 | 1. Get access to https://anaconda.org/scientific-python-nightly-wheels/, the location used to host nightly wheels 90 | 2. Setup a CI step that builds wheels for your project 91 | 3. Setup a CI step that uploads wheels to https://anaconda.org/scientific-python-nightly-wheels/ 92 | 93 | For step (1), visit https://github.com/scientific-python/upload-nightly-action and create an issue 94 | requesting access. List the project you maintain and would like to upload nightlies for. We 95 | will reply to the issue and let you know what happens next. 96 | 97 | The work for step (2) depends on your project. You are probably already doing this for your 98 | releases. The part to remember is building wheels regularly, at least once a week. 99 | 100 | For step (3), there is a GitHub Action that you can use. You can find the action at 101 | https://github.com/scientific-python/upload-nightly-action. 102 | To use it in your "build wheels workflow", add the following lines as an additional step: 103 | 104 | ``` 105 | - name: Upload wheel 106 | uses: scientific-python/upload-nightly-action@main 107 | with: 108 | artifacts_path: dist/ 109 | anaconda_nightly_upload_token: ${{ secrets.UPLOAD_TOKEN }} 110 | ``` 111 | 112 | Complete examples of how projects implement this in their CI setup are linked in the Notes section. 113 | 114 | #### Artifact cleanup-policy at https://anaconda.org/scientific-python-nightly-wheels 115 | 116 | To avoid hosting outdated development versions, as well as to clean up space, we do have a 117 | retention policy of: 118 | 119 | - Latest **N versions** 120 | - Artifacts newer than **M days** 121 | 122 | Any versions beyond these are automatically removed as part of a daily cron job. Projects may 123 | have reasons to request to be added to the list exempt from this automated cleanup, however in that 124 | case the responsibility of cleaning-up old, unused versions fall back on the individual project. 125 | 126 | Note: The actual values for `N` and `M` are defined and documented in the 127 | https://github.com/scientific-python/upload-nightly-action repository. 128 | 129 | #### Process for Adding New Projects 130 | 131 | The site admins are drawn from active members from the scientific Python community. 132 | Ideally, the collection of admins comprises a broad selection of community 133 | members across different projects and underlying organizations. 134 | This is to ensure community ownership of the wheel-hosting infrastructure and 135 | administration governed by consensus, as opposed to unilateral 136 | decision-making by any individual, project, or organization. 137 | Adding new administrators requires opening an issue. 138 | After a project creates an issue on https://github.com/scientific-python/upload-nightly-action 139 | requesting access to upload wheels, an admin has to respond to the request. 140 | 141 | We wish to stay open to new projects uploading wheels with us. At the same time, we need to 142 | perform some due diligence before giving access since approved projects gain the broad exposure 143 | within the Scientific Python ecosystem. This could be abused by malicious actors. 144 | 145 | A project's chosen representatives should each create an 146 | account on https://anaconda.org and share their usernames with the 147 | admins of the Scientific Python organization on Anaconda. 148 | To increase resilience, we suggest that each project have at least two registered 149 | representatives. 150 | 151 | The representative can then generate a personal access token at 152 | https://anaconda.org/[user]/settings/access and use it in CI to upload 153 | wheels. 154 | The token should only have the "Allow uploads to Standard Python repositories", 155 | "Allow read access to the API site" and "Allow write access to the API site" scope. 156 | The creation of tokens at the organization level should be avoided for security reasons. 157 | 158 | The next step is to make an initial upload of a wheel to create the package listing on anaconda.org. 159 | Once this operation is done, you can revoke your token and add the new user to its project. 160 | Each project should have at least one user who is also an admin of the project. 161 | 162 | At that point, let the user know that they have been added and that they can create a personal 163 | access token (as outlined above.) They can now upload new wheels and perform maintenance 164 | actions on their project. 165 | 166 | ## Notes 167 | 168 | 172 | 173 | - [GitHub Action workflow for building and uploading scikit-learn wheels](https://github.com/scikit-learn/scikit-learn/blob/f034f57b1ad7bc5a7a5dd342543cea30c85e74ff/.github/workflows/wheels.yml) 174 | as an example of how to build wheels and upload them to the nightly area. 175 | - [GitHub Action workflow for building and uploading NumPy wheels](https://github.com/numpy/numpy/blob/cc0abd768575d7f9e862de0b4912af27f6e9690d/.github/workflows/wheels.yml) 176 | - Example of [a GitHub Action workflow that creates a tracking issue for failed CI runs](https://github.com/scikit-learn/scikit-learn/blob/689efe2f25356aa674bd0090f44b0914aae4d3a3/.github/workflows/update_tracking_issue.yml) 177 | - Example of using [this action in NetworkX](https://github.com/networkx/networkx/blob/main/.github/workflows/nightly.yml) to publish a nightly release. 178 | - Example of [a Jupyter notebook based tutorial repo](https://github.com/numpy/numpy-tutorials/blob/main/tox.ini) to test with multiple version combination, including using the nightly wheels for the development version. 179 | - `astropy` ships [its own nightly wheel](https://docs.astropy.org/en/latest/install.html#installing-pre-built-development-versions-of-astropy) 180 | using [a workflow that relies on OpenAstronomy](https://github.com/astropy/astropy/blob/main/.github/workflows/publish.yml). 181 | Its direct dependency, `pyerfa`, also has nightly wheel [here](https://pypi.anaconda.org/liberfa/simple). 182 | -------------------------------------------------------------------------------- /spec-0009/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 9 — Governance" 3 | number: 9 4 | date: 2024-06-04 5 | author: 6 | - "Sanket Verma " 7 | - "Inessa Pawson " 8 | - "Daniel McCloy " 9 | - "Matt Haberland " 10 | - "Jarrod Millman " 11 | is-draft: true 12 | endorsed-by: 13 | --- 14 | 15 | ## Description 16 | 17 | This SPEC describes what governance is, and offers recommendations for choosing a 18 | governance model for open-source projects. 19 | 20 | Open-source project communities are amalgamations of software and the humans who 21 | use and maintain it. Governance models define methods for decision-making 22 | within a project, roles for the community members imbued with decision-making 23 | authority, and processes for filling (and, if needed, vacating) those 24 | leadership roles. In the context of open-source software projects, governance 25 | models often also define the granting of necessary permissions or access to 26 | organization secrets necessary for the implementation of project-related 27 | decisions. Governance models are complementary to Codes of Conduct and 28 | Community Guidelines, which define the expectations and responsibilities of all 29 | participants in a project (including users of the software), regardless of 30 | their status as a project leader (cf. SPEC XXX), ensuring the long-term 31 | sustainability of a project. 32 | 33 | The open source communities interact and contribute through various mediums, 34 | such as distributed version control systems, chat platforms, blog posts, 35 | discussion forums, mailing lists, and social media. Having clear guidelines for 36 | participation helps maintain a continuous stream of contributions. 37 | 38 | This SPEC outlines the definition of governance, various open-source governance 39 | models, the steps to choose the right governance model, and what needs to be 40 | done post-adoption. 41 | 42 | Please note that these are recommendations and not bylaws. If you have 43 | suggestions/thoughts, we're more than happy to consider them. 44 | 45 | ### Core Project Endorsement 46 | 47 | 50 | 51 | ### Ecosystem Adoption 52 | 53 | 56 | 57 | #### Badges 58 | 59 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 60 | {{< spec_badge number="9" title="Governance" >}} 61 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 62 | 63 | ## Implementation 64 | 65 | ### Choosing a governance model 66 | 67 | When you define governance for a project, you need to identify a few things: 68 | 69 | - What roles does maintainers, core team, contributors, and community members 70 | play in the project? 71 | - What authority, duties, and privileges are associated with each role? 72 | - How do people get assigned to and removed from roles? 73 | - How are decisions made and documented within the project? 74 | - What are the methods for resolving conflicts? 75 | 76 | There are few **pre-defined models** for governance used across open-source 77 | projects. We'll mention them here for reference: 78 | 79 | - BDFL 80 | - BDFL stands for 'Benevolent Dictator for Life.' Under this structure, one 81 | person (usually the project's initial author) has the final say on all 82 | major project decisions. Smaller projects are BDFL by default because 83 | there are only one or two maintainers. 84 | - BDFL model template → http://oss-watch.ac.uk/resources/benevolentdictatorgovernancemodel 85 | - Meritocracy / Do-ocracy 86 | - Decisions are made by contributors who have demonstrated merit through 87 | their contributions to the project. Merit is often gained through 88 | code contributions, documentation improvements, or other significant efforts. 89 | - Meritocracy model template → http://oss-watch.ac.uk/resources/meritocraticgovernancemodel 90 | - Self appointing council / committee / board 91 | - This model appoints a board or committee to govern various aspects of a 92 | project. Those groups are often called the steering committee, committer 93 | council, technical operation committee, board of directors, etc. This 94 | model might be helpful for projects that need a sponsoring foundation, 95 | and projects for which establishing electoral mechanisms is challenging. 96 | - Electoral 97 | - The community elects the leadership or decision-making 98 | body. This model aims to ensure that those who guide the project's 99 | direction and make crucial decisions are chosen democratically, 100 | representing the broader community's interests. 101 | - Foundation backed 102 | - A non-profit foundation oversees the project, and its governance 103 | structures often include a board of directors, technical committees, and 104 | working groups. 105 | 106 | **Other governance models** 107 | 108 | - Liberal contribution 109 | - Under a liberal contribution model, the people who do the most work are 110 | recognized as influential. Major project decisions are made based on a 111 | consensus-seeking process (discussing major grievances) rather than pure 112 | vote, and the team strives to include as many community perspectives as 113 | possible. 114 | - Example: Node.js liberal contribution policy → https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951 115 | - C4 (Collective Code Construction Contract) 116 | - https://rfc.zeromq.org/spec/22/ 117 | 118 | ### How to pick the appropriate governance model? 119 | 120 | Choosing the right governance model for your open-source project involves 121 | considering several factors to ensure the structure aligns with your project’s 122 | goals, community, and resources. Most open-source projects start with the BDFL 123 | model; eventually, they grow and adopt more open models. 124 | 125 | A few things you should consider while choosing the appropriate model for your project: 126 | 127 | - **Project lifecycle stage** 128 | - Early stage 129 | - Growth stage 130 | - Mature stage 131 | - **Assess your project's needs and goals** 132 | - Project size and complexity 133 | - Community involvement 134 | - **Evaluate available resources** 135 | - Leadership and management capacity 136 | - **Community dynamics** 137 | - Large, diverse communities might benefit from democratic or meritocratic 138 | models to represent various interests 139 | - Smaller, homogeneous communities are more suited for a BDFL model 140 | - **Technical and Operational Complexity** 141 | - Complex projects with numerous sub-projects or components may need a 142 | structured governance model to manage them effectively 143 | - **Flexibility and Adaptability** 144 | - Choose a model that allows for future adjustments as the project evolves 145 | - Implement mechanisms for regular feedback from the community on governance 146 | effectiveness 147 | - **External Stakeholders** 148 | - Consider how partnerships with other projects, organizations, or companies 149 | might influence governance 150 | - **Legal and financial considerations** 151 | - If the project needs to be incorporated as a non-profit, foundation, or 152 | under a company 153 | - Assess the sources of funding and how they influence governance 154 | 155 | ### What to do after the adoption? 156 | 157 | Once a project has adopted a governance model, the several important steps could 158 | be followed to ensure smooth implementation and operation. 159 | 160 | - **Document the governance model** 161 | - Clearly document the governance model, including roles, responsibilities, 162 | decision-making processes, and procedures for conflict resolution. 163 | - https://communityrule.info/templates/ offers an excellent interface where 164 | you can create a `.md` for your governance model 165 | - **Communicate to the Community** 166 | - Make a public announcement about the adoption of the new governance model 167 | - Provide an overview of how the governance model works, why it was chosen, 168 | and what it means for the community 169 | - **Implement the governance structure** 170 | - Assign initial roles and responsibilities 171 | - Form any necessary committees, councils, or boards as defined by the 172 | governance model 173 | - **Fostering community engagement and ensuring transparency and 174 | accountability** 175 | - Provide regular updates and reports to the community on governance 176 | activities, decisions, and project progress 177 | - Establish mechanisms for the community to provide feedback on the 178 | governance model and its implementation 179 | - Maintain transparency by making all governance-related documentation, 180 | meeting minutes, and decisions publicly available. You can use public 181 | repository or a website for this. 182 | - **Establish Conflict Resolution Processes** 183 | - Implement formal mechanisms for resolving conflicts within the community 184 | and governance bodies. Ensure these mechanisms are fair, transparent, 185 | account for diversity and inclusion, and accessible to all community 186 | members. 187 | 188 | ## Notes 189 | 190 | - OSSWatch resource on [Governance models](http://oss-watch.ac.uk/resources/governancemodels) 191 | 192 | - Chapter - [Project and Community Governance](https://www.theopensourceway.org/the_open_source_way-guidebook-2.0.html#_project_and_community_governance) from [The Open Source Way](https://www.theopensourceway.org/) Guidebook 193 | -------------------------------------------------------------------------------- /spec-0007/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 7 — Seeding Pseudo-Random Number Generation" 3 | number: 7 4 | date: 2023-04-19 5 | author: 6 | - "Stéfan van der Walt " 7 | - "Sebastian Berg " 8 | - "Pamphile Roy " 9 | - "Matt Haberland " 10 | endorsed-by: 11 | - ipython 12 | - networkx 13 | - numpy 14 | - scikit-image 15 | - scipy 16 | --- 17 | 18 | ## Description 19 | 20 | Currently, libraries across the ecosystem provide various APIs for seeding pseudo-random number generation. 21 | This SPEC suggests a unified, pragmatic API, taking into account technical and historical factors. 22 | Adopting such a uniform API will simplify the user experience, especially for those who rely on multiple projects. 23 | 24 | We recommend: 25 | 26 | - standardizing the usage and interpretation of an `rng` keyword for seeding, and 27 | - avoiding the use of global state and legacy bitstream generators. 28 | 29 | We suggest implementing these principles by: 30 | 31 | - deprecating uses of an existing seed argument (commonly `random_state` or `seed`) in favor of a consistent `rng` argument, 32 | - using `numpy.random.default_rng` to normalize the `rng` argument and instantiate a `Generator`[^no-RandomState], and 33 | - deprecating the use of `numpy.random.seed` to control the random state. 34 | 35 | We are primarily concerned with API uniformity, but also encourage libraries to move towards using [NumPy pseudo-random `Generator`s](https://numpy.org/doc/stable/reference/random/generator.html) because: 36 | 37 | 1. `Generator`s avoid problems associated with naïve seeding (e.g., using successive integers), via its [SeedSequence](https://numpy.org/doc/stable/reference/random/parallel.html#seedsequence-spawning) mechanism; 38 | 2. their use avoids relying on global state—which can make code execution harder to track, and may cause problems in parallel processing scenarios. 39 | 40 | [^no-RandomState]: 41 | Note that in NumPy versions prior to 2.2.0, `numpy.random.default_rng` does not accept instances of `RandomState`. 42 | In more recent versions, `numpy.random.default_rng` will convert `RandomState` instances to `Generator`s, which may not behave identically even with identical method calls. 43 | That said, neither `np.random.seed` nor `np.random.RandomState` _themselves_ are deprecated, so they may still be used directly in some contexts (e.g. by developers for generating unit test data). 44 | 45 | ### Scope 46 | 47 | This is intended as a recommendation to all libraries that allow users to control the state of a NumPy random number generator. 48 | It is specifically targeted toward functions that currently accept random number seeds using an argument other than `rng`, rely on the particular behavior of `RandomState` methods, or allow `numpy.random.seed` to control the random state, but the ideas are more broadly applicable. 49 | Random number generators other than those provided by NumPy could also be accommodated by an `rng` keyword, but that is beyond the scope of this SPEC. 50 | 51 | ### Concepts 52 | 53 | - `BitGenerator`: Generates a stream of pseudo-random bits. The default generator in NumPy (`numpy.random.default_rng`) uses PCG64. 54 | - `Generator`: Derives pseudo-random numbers from the bits produced by a `BitGenerator`. 55 | - `RandomState`: a [legacy object in NumPy](https://numpy.org/doc/stable/reference/random/index.html), similar to `Generator`, that produces random numbers based on the Mersenne Twister. 56 | 57 | ### Constraints 58 | 59 | NumPy, SciPy, scikit-learn, scikit-image, and NetworkX all implement pseudo-random seeding in slightly different ways. 60 | Common keyword arguments include `random_state` and `seed`. 61 | In practice, the seed is also often controllable using `numpy.random.seed`. 62 | 63 | ### Core Project Endorsement 64 | 65 | Endorsement of this SPEC means that a project considers the standardization and interpretation of the `rng` keyword, as well as avoiding use of global state and legacy bitstream generators, good ideas that are worth implemented widely. 66 | 67 | ### Ecosystem Adoption 68 | 69 | To adopt this SPEC, a project should: 70 | 71 | - deprecate the use of `random_state`/`seed` arguments in favor of an `rng` argument in all functions where users need to control pseudo-random number generation, 72 | - use `numpy.random.default_rng` to normalize the `rng` argument and instantiate a `Generator`, and 73 | - deprecate the use of `numpy.random.seed` to control the random state. 74 | 75 | #### Badges 76 | 77 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 78 | {{< spec_badge number="7" title="Seeding pseudo-random number generation" >}} 79 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 80 | 81 | ## Implementation 82 | 83 | Legacy behavior in packages such as scikit-learn (`sklearn.utils.check_random_state`) typically handle `None` (use the global seed state), an int (convert to `RandomState`), or `RandomState` object. 84 | 85 | Our recommendation here is a deprecation strategy which does not in _all_ cases adhere to the Hinsen principle[^hinsen], 86 | although it could very nearly do so by enforcing the use of `rng` as a keyword argument. 87 | 88 | [^hinsen]: The Hinsen principle states, loosely, that code should, whether executed now or in the future, return the same result, or raise an error. 89 | 90 | The [deprecation strategy](https://github.com/scientific-python/specs/pull/180#issuecomment-1515248009) is as follows. 91 | 92 | **Initially**, accept both `rng` and the existing `random_state`/`seed`/`...` keyword arguments. 93 | 94 | - If both are specified by the user, raise an error. 95 | - If `rng` is passed by keyword, normalize it with `np.random.default_rng()` and use it to generate random numbers as needed. 96 | - If `random_state`/`seed`/`...` is specified (by keyword or position, if allowed), preserve existing behavior. 97 | 98 | **After `rng` becomes available** in all releases within the support window suggested by SPEC 0, emit warnings as follows: 99 | 100 | - If neither `rng` nor `random_state`/`seed`/`...` is specified and `np.random.seed` has been used to set the seed, emit a `FutureWarning` about the upcoming change in behavior. 101 | - If `random_state`/`seed`/`...` is passed by keyword or by position, treat it as before, but: 102 | - Emit a `DeprecationWarning` if passed by keyword, warning about the deprecation of keyword `random_state` in favor of `rng`. 103 | - Emit a `FutureWarning` if passed by position, warning about the change in behavior of the positional argument. 104 | 105 | **After the deprecation period**, accept only `rng`, raising an error if `random_state`/`seed`/`...` is provided. 106 | 107 | By now, the function signature, with type annotations, could look like this: 108 | 109 | ```python 110 | from collections.abc import Sequence 111 | import numpy as np 112 | 113 | 114 | SeedLike = int | np.integer | Sequence[int] | np.random.SeedSequence 115 | RNGLike = np.random.Generator | np.random.BitGenerator 116 | 117 | 118 | def my_func(*, rng: RNGLike | SeedLike | None = None): 119 | """My function summary. 120 | 121 | Parameters 122 | ---------- 123 | rng : `numpy.random.Generator`, optional 124 | Pseudorandom number generator state. When `rng` is None, a new 125 | `numpy.random.Generator` is created using entropy from the 126 | operating system. Types other than `numpy.random.Generator` are 127 | passed to `numpy.random.default_rng` to instantiate a ``Generator``. 128 | """ 129 | rng = np.random.default_rng(rng) 130 | 131 | ... 132 | 133 | ``` 134 | 135 | Also note the suggested language for the `rng` parameter docstring, which encourages the user to pass a `Generator` or `None`, but allows for other types accepted by `numpy.random.default_rng` (captured by the type annotation). 136 | 137 | ### Impact 138 | 139 | There are three classes of users, which will be affected to varying degrees. 140 | 141 | 1. Those who do not attempt to control the random state. 142 | Their code will switch from using the unseeded global `RandomState` to using an unseeded `Generator`. 143 | Since the underlying _distributions_ of pseudo-random numbers will not change, these users should be largely unaffected. 144 | While _technically_ this change does not adhere to the Hinsen principle, its impact should be minimal. 145 | 146 | 2. Users of `random_state`/`seed` arguments. 147 | Support for these arguments will be dropped eventually, but during the deprecation period, we can provide clear guidance, via warnings and documentation, on how to migrate to the new `rng` keyword. 148 | 149 | 3. Those who use `numpy.random.seed`. 150 | The proposal will do away with that global seeding mechanism, meaning that code that relies on it would, after the deprecation period, go from being seeded to being unseeded. 151 | To ensure that this does not go unnoticed, libraries that allowed for control of the random state via `numpy.random.seed` should raise a `FutureWarning` if `np.random.seed` has been called. (See [Code](#code) below for an example.) 152 | To fully adhere to the Hinsen principle, these warnings should instead be raised as errors. 153 | In response, users will have to switch from using `numpy.random.seed` to passing the `rng` argument explicitly to all functions that accept it. 154 | 155 | ### Code 156 | 157 | As an example, consider how a SciPy function would transition from a `random_state` parameter to an `rng` parameter using a decorator. 158 | 159 | {{< include-code "transition_to_rng.py" "python" >}} 160 | 161 | ## Notes 162 | -------------------------------------------------------------------------------- /spec-0007/transition_to_rng.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import functools 3 | import inspect 4 | import warnings 5 | 6 | 7 | def _transition_to_rng(old_name, *, position_num=None, end_version=None): 8 | """Example decorator to transition from old PRNG usage to new `rng` behavior 9 | 10 | Suppose the decorator is applied to a function that used to accept parameter 11 | `old_name='random_state'` either by keyword or as a positional argument at 12 | `position_num=1`. At the time of application, the name of the argument in the 13 | function signature is manually changed to the new name, `rng`. If positional 14 | use was allowed before, this is not changed.* 15 | 16 | - If the function is called with both `random_state` and `rng`, the decorator 17 | raises an error. 18 | - If `random_state` is provided as a keyword argument, the decorator passes 19 | `random_state` to the function's `rng` argument as a keyword. If `end_version` 20 | is specified, the decorator will emit a `DeprecationWarning` about the 21 | deprecation of keyword `random_state`. 22 | - If `random_state` is provided as a positional argument, the decorator passes 23 | `random_state` to the function's `rng` argument by position. If `end_version` 24 | is specified, the decorator will emit a `FutureWarning` about the changing 25 | interpretation of the argument. 26 | - If `rng` is provided as a keyword argument, the decorator validates `rng` using 27 | `numpy.random.default_rng` before passing it to the function. 28 | - If `end_version` is specified and neither `random_state` nor `rng` is provided 29 | by the user, the decorator checks whether `np.random.seed` has been used to set 30 | the global seed. If so, it emits a `FutureWarning`, noting that usage of 31 | `numpy.random.seed` will eventually have no effect. Either way, the decorator 32 | calls the function without explicitly passing the `rng` argument. 33 | 34 | If `end_version` is specified, a user must pass `rng` as a keyword to avoid warnings. 35 | 36 | After the deprecation period, the decorator can be removed, and the function 37 | can simply validate the `rng` argument by calling `np.random.default_rng(rng)`. 38 | 39 | * A `FutureWarning` is emitted when the PRNG argument is used by 40 | position. It indicates that the "Hinsen principle" (same 41 | code yielding different results in two versions of the software) 42 | will be violated, unless positional use is deprecated. Specifically: 43 | 44 | - If `None` is passed by position and `np.random.seed` has been used, 45 | the function will change from being seeded to being unseeded. 46 | - If an integer is passed by position, the random stream will change. 47 | - If an instance of `RandomState` is passed by position, either an 48 | error will be raised (NumPy < 2.2.0), or it will be accepted 49 | (NumPy >= 2.2.0) but the random numbers generated may be different 50 | from those that would be produced by the original `RandomState`. 51 | - If `np.random` is passed by position, an error will be raised. 52 | 53 | We suggest that projects consider deprecating positional use of 54 | `random_state`/`rng` (i.e., change their function signatures to 55 | ``def my_func(..., *, rng=None)``); that might not make sense 56 | for all projects, so this SPEC does not make that 57 | recommendation, neither does this decorator enforce it. 58 | 59 | Parameters 60 | ---------- 61 | old_name : str 62 | The old name of the PRNG argument (e.g. `seed` or `random_state`). 63 | position_num : int, optional 64 | The (0-indexed) position of the old PRNG argument (if accepted by position). 65 | Maintainers are welcome to eliminate this argument and use, for example, 66 | `inspect`, if preferred. 67 | end_version : str, optional 68 | The full version number of the library when the behavior described in 69 | `DeprecationWarning`s and `FutureWarning`s will take effect. If left 70 | unspecified, no warnings will be emitted by the decorator. 71 | 72 | """ 73 | NEW_NAME = "rng" 74 | 75 | cmn_msg = ( 76 | "To silence this warning and ensure consistent behavior in SciPy " 77 | f"{end_version}, control the RNG using argument `{NEW_NAME}`. Arguments passed " 78 | f"to keyword `{NEW_NAME}` will be validated by `np.random.default_rng`, so the " 79 | "behavior corresponding with a given value may change compared to use of " 80 | f"`{old_name}`. For example, " 81 | "1) `None` will result in unpredictable random numbers, " 82 | "2) an integer will result in a different stream of random numbers, (with the " 83 | "same distribution), and " 84 | "3) `np.random` or `RandomState` instances will result in an error. " 85 | "See the documentation of `default_rng` for more information." 86 | ) 87 | 88 | def decorator(fun): 89 | @functools.wraps(fun) 90 | def wrapper(*args, **kwargs): 91 | # Determine how PRNG was passed 92 | as_old_kwarg = old_name in kwargs 93 | as_new_kwarg = NEW_NAME in kwargs 94 | as_pos_arg = position_num is not None and len(args) >= position_num + 1 95 | emit_warning = end_version is not None 96 | 97 | # Can only specify PRNG one of the three ways 98 | if int(as_old_kwarg) + int(as_new_kwarg) + int(as_pos_arg) > 1: 99 | message = ( 100 | f"{fun.__name__}() got multiple values for " 101 | f"argument now known as `{NEW_NAME}`" 102 | ) 103 | raise TypeError(message) 104 | 105 | # Check whether global random state has been set 106 | global_seed_set = np.random.mtrand._rand._bit_generator._seed_seq is None 107 | 108 | if as_old_kwarg: # warn about deprecated use of old kwarg 109 | kwargs[NEW_NAME] = kwargs.pop(old_name) 110 | if emit_warning: 111 | message = ( 112 | f"Use of keyword argument `{old_name}` is " 113 | f"deprecated and replaced by `{NEW_NAME}`. " 114 | f"Support for `{old_name}` will be removed " 115 | f"in SciPy {end_version}." 116 | ) + cmn_msg 117 | warnings.warn(message, DeprecationWarning, stacklevel=2) 118 | 119 | elif as_pos_arg: 120 | # Warn about changing meaning of positional arg 121 | 122 | # Note that this decorator does not deprecate positional use of the 123 | # argument; it only warns that the behavior will change in the future. 124 | # Simultaneously transitioning to keyword-only use is another option. 125 | 126 | arg = args[position_num] 127 | # If the argument is None and the global seed wasn't set, or if the 128 | # argument is one of a few new classes, the user will not notice change 129 | # in behavior. 130 | ok_classes = ( 131 | np.random.Generator, 132 | np.random.SeedSequence, 133 | np.random.BitGenerator, 134 | ) 135 | if (arg is None and not global_seed_set) or isinstance(arg, ok_classes): 136 | pass 137 | elif emit_warning: 138 | message = ( 139 | f"Positional use of `{NEW_NAME}` (formerly known as " 140 | f"`{old_name}`) is still allowed, but the behavior is " 141 | "changing: the argument will be normalized using " 142 | f"`np.random.default_rng` beginning in SciPy {end_version}, " 143 | "and the resulting `Generator` will be used to generate " 144 | "random numbers." 145 | ) + cmn_msg 146 | warnings.warn(message, FutureWarning, stacklevel=2) 147 | 148 | elif as_new_kwarg: # no warnings; this is the preferred use 149 | # After the removal of the decorator, normalization with 150 | # np.random.default_rng will be done inside the decorated function 151 | kwargs[NEW_NAME] = np.random.default_rng(kwargs[NEW_NAME]) 152 | 153 | elif global_seed_set and emit_warning: 154 | # Emit FutureWarning if `np.random.seed` was used and no PRNG was passed 155 | message = ( 156 | "The NumPy global RNG was seeded by calling " 157 | f"`np.random.seed`. Beginning in {end_version}, this " 158 | "function will no longer use the global RNG." 159 | ) + cmn_msg 160 | warnings.warn(message, FutureWarning, stacklevel=2) 161 | 162 | return fun(*args, **kwargs) 163 | 164 | # Add the old parameter name to the function signature 165 | wrapped_signature = inspect.signature(fun) 166 | wrapper.__signature__ = wrapped_signature.replace( 167 | parameters=[ 168 | *wrapped_signature.parameters.values(), 169 | inspect.Parameter( 170 | old_name, inspect.Parameter.KEYWORD_ONLY, default=None 171 | ), 172 | ] 173 | ) 174 | 175 | return wrapper 176 | 177 | return decorator 178 | 179 | 180 | # Example usage of _prepare_rng decorator. 181 | 182 | # Suppose a library uses a custom random state normalisation function, such as 183 | from scipy._lib._util import check_random_state 184 | 185 | # https://github.com/scipy/scipy/blob/94532e74b902b569bfad504866cb53720c5f4f31/scipy/_lib/_util.py#L253 186 | 187 | 188 | # Suppose a function `library_function` is defined as: 189 | def library_function(arg1, random_state=None, arg2=0): 190 | random_state = check_random_state(random_state) 191 | return random_state.random() * arg1 + arg2 192 | 193 | 194 | # We apply the decorator and change the function signature at the same time. 195 | # The use of `random_state` throughout the function may be replaced with `rng`, 196 | # or the variable may be defined as `random_state = rng`. 197 | @_transition_to_rng("random_state", position_num=1) 198 | def library_function(arg1, rng=None, arg2=0): 199 | rng = check_random_state(rng) 200 | return rng.random() * arg1 + arg2 201 | 202 | 203 | # After `rng` is available in all releases within the support window suggested by 204 | # SPEC 0, we pass the `end_version` param to the decorator to emit warnings. 205 | @_transition_to_rng("random_state", position_num=1, end_version="1.17.0") 206 | def library_function(arg1, rng=None, arg2=0): 207 | rng = check_random_state(rng) 208 | return rng.random() * arg1 + arg2 209 | 210 | 211 | # At the end of the deprecation period, remove the decorator, and normalize 212 | # `rng` with` np.random.default_rng`. 213 | def library_function(arg1, rng=None, arg2=0): 214 | rng = np.random.default_rng(rng) 215 | return rng.random() * arg1 + arg2 216 | -------------------------------------------------------------------------------- /spec-0008/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 8 — Securing the Release Process" 3 | number: 8 4 | date: 2024-06-04 5 | author: 6 | - "Matthew Feickert " 7 | - "Pamphile Roy " 8 | - "Juanita Gomez " 9 | - "Seth Larson " 10 | - "Lars Grüter " 11 | - "Jarrod Millman " 12 | endorsed-by: 13 | - networkx 14 | - numpy 15 | - scikit-image 16 | - scikit-learn 17 | --- 18 | 19 | ## Description 20 | 21 | 25 | 26 | Open source libraries constitute a significant portion of the world's digital infrastructure. Securing the Open Source supply chain (OSSC) is therefore an increasing concern, with examples of sophisticated attacks against the ecosystem (e.g., the 2024 [`xz` utils backdoor](https://en.wikipedia.org/wiki/XZ_Utils_backdoor)) and [malware attacks on PyPI](https://blog.pypi.org/posts/2024-04-10-domain-abuse/) highlighting the need for supply chain security to be taken seriously. 27 | The Python Software Foundation (PSF) is also taking the importance of the OSSC seriously, as demonstrated by the [creation of the PSF Security Developer in Residence position in 2023](https://pyfound.blogspot.com/2023/06/announcing-our-new-security-developer.html). 28 | 29 | With the [Supply-chain Levels for Software Artifacts (SLSA) framework](https://slsa.dev/) and [OpenID Connect (OIDC) standard](https://openid.net/developers/how-connect-works/) being widely adopted, several high level developer tools, maintained by professional security teams, have been created with clear recommendations on how to use them. 30 | 31 | This SPEC outlines pragmatic recommendations for adopting these security tools and recommendations on how to publish release artifacts securely. 32 | Securely _building_ release artifacts will be covered in a later SPEC. This set of recommendations complements the recommendations from [SPEC 6 — Keys to the Castle](https://github.com/scientific-python/specs/blob/main/spec-0006/index.md). 33 | 34 | While this SPEC is written with GitHub in mind, the same recommendations apply to other services, such as GitLab. 35 | 36 | #### Badges 37 | 38 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 39 | {{< spec_badge number="8" title="Securing the Release Process" >}} 40 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 41 | 42 | ## Implementation 43 | 44 | With a focus on securing the release artifact distribution process, the following processes and standards should be adopted. 45 | 46 | ### Document the release process 47 | 48 | The release process should be clearly and fully documented in the developer documentation and describe each step to make a release and the permissions required to do so. 49 | It is recommended that this is a dedicated page in the developer section of the documentation website, though providing instructions in a `RELEASING.md` in the top level of the repository is also a common approach. 50 | 51 | ### Hardening workflow environment permissions 52 | 53 | - Workflows that publish release artifacts should have _run triggers_ that require intentional actions by the release team (e.g., `workflow_dispatch` in GitHub Actions) and require multiple release team members to approve the workflow to run (c.f. "Use GitHub Actions environments" section below). 54 | This is to safeguard the project from any one maintainer having the ability to commit to the default branch and make a release directly. 55 | 56 | - It is also strongly recommended that release managers use [signed commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits), so that each release corresponds to a verified commit. Note that it can be difficult to enforce this via GitHub permissions without requiring all contributors to also sign their commits, which may be undesirable for many projects. 57 | - The branch from which the release is made should also be protected. 58 | 59 | #### Restrict permissions in CI runners to the minimum required 60 | 61 | To restrict the attack surface area of arbitrary code execution in CI runners, the _default_ runner permissions should be restricted to the minimum possible (read access). In the GitHub Action workflow, this is accomplished by defining the following workflow global permissions block before any jobs are defined. 62 | 63 | ```yaml 64 | permissions: 65 | contents: read 66 | ``` 67 | 68 | Elevating permissions beyond this should be done at the job level by redefining the permissions block in the job. 69 | 70 | #### Restrict permitted actions in workflows 71 | 72 | GitHub allows restricting the actions that workflows can use via the repository actions permissions settings at `https://github.com/$ORG/$PROJECT/settings/actions`. 73 | A reasonable default is to select the 74 | 75 | - Allow `$ORG`, and select `non-$ORG`, actions and reusable workflows 76 | 77 | option and the suboptions: 78 | 79 | - Allow actions created by GitHub 80 | - Allow specified actions and reusable workflows 81 | 82 | Consult [Managing GitHub Actions permissions for your repository](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#managing-github-actions-permissions-for-your-repository) for more details. 83 | 84 | #### Use GitHub Actions environments 85 | 86 | Use a [GitHub Actions environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) 87 | 88 | ```yaml 89 | environment: 90 | name: publish-package 91 | ``` 92 | 93 | and enforce additional review by at least one other release team maintainer to run a GitHub Actions workflow that publishes to PyPI. 94 | Additional reviewer requirements can be configured per GitHub Actions environment under `https://github.com/$ORG/$PROJECT/settings/environments/` in the "Deployment protection rules" section. 95 | 96 | ![github-actions-environment](https://hackmd.io/_uploads/S1SErQ0EC.png) 97 | 98 | ### Pin GitHub Actions release workflows to their full release commit SHAs 99 | 100 | GitHub actions must be pinned using full commit SHA corresponding to the release version being used. 101 | Using versions or small hashes is susceptible to attacks. 102 | 103 | ```yaml 104 | - uses: actions/some-action@1fe14e04876783b259436247a3898d2fe7d5548f #vX.Y.Z 105 | ``` 106 | 107 | Dependabot can be used to automatically update the hashes. 108 | It is important that this happens as part of a reviewed process. 109 | 110 | ```yaml! 111 | # .github/dependabot.yml 112 | version: 2 113 | updates: 114 | # Maintain dependencies for GitHub Actions 115 | - package-ecosystem: "github-actions" 116 | directory: "/" 117 | schedule: 118 | interval: "monthly" 119 | groups: 120 | actions: 121 | patterns: 122 | - "*" 123 | ``` 124 | 125 | ### Adopt SLSA through use of GitHub Attestations 126 | 127 | A component of SLSA is [software attestation](https://slsa.dev/attestation-model) which allows for public validation of software artifacts and provenance. 128 | GitHub provides the [`actions/attest-build-provenance`](https://github.com/actions/attest-build-provenance) GitHub Action which implements SLSA to generate signed build provenance attestations for workflow artifacts. 129 | Attestations are published to the project GitHub under `https://github.com/$ORG/$PROJECT/attestations/`. 130 | 131 | ```yaml 132 | - uses: actions/attest-build-provenance@ # vX.Y.Z 133 | with: 134 | subject-path: "dist/-*" 135 | ``` 136 | 137 | GitHub has also added the [`gh attestation verify`](https://cli.github.com/manual/gh_attestation_verify) command to the GitHub CLI utility, which verifies the integrity and provenance of an artifact using its associated cryptographically signed attestations. 138 | This can be used by individual users and also in GitHub Actions workflows where the GitHub CLI utility is installed by default. 139 | 140 | ```yaml 141 | - name: Verify artifact attestation 142 | env: 143 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 144 | shell: bash 145 | run: | 146 | for artifact in dist/*; do 147 | echo "# ${artifact}" 148 | gh attestation verify "${artifact}" --repo ${{ github.repository }} 149 | done 150 | ``` 151 | 152 | ### Adopt OIDC through the use of PyPI Trusted Publishers 153 | 154 | [Trusted Publishers](https://docs.pypi.org/trusted-publishers/) provide a way to securely establish a short lived authentication token between a project repository and a distribution platform — such as PyPI. 155 | It replaces the need to use a long lived token to authenticate, reducing the security risks associated with authentication tokens (e.g., tokens being compromised, the need to rotate tokens). 156 | 157 | Trusted Publishers can be used in GitHub Actions by using the [`pypa/gh-action-pypi-publish`](https://github.com/pypa/gh-action-pypi-publish) GitHub Action defaults in a GitHub Actions environment. 158 | 159 | ```yaml 160 | jobs: 161 | publish: 162 | name: Publish release to PyPI 163 | runs-on: ubuntu-latest 164 | environment: publish-package 165 | permissions: 166 | # IMPORTANT: this permission is mandatory for trusted publishing 167 | id-token: write 168 | steps: 169 | # retrieve your distributions here 170 | # ... 171 | 172 | - name: Publish distribution to PyPI 173 | uses: pypa/gh-action-pypi-publish@ # vX.Y.Z 174 | with: 175 | print-hash: true 176 | ``` 177 | 178 | ### Example workflow 179 | 180 | The following is a complete example of a workflow which can be used as a starting point: 181 | 182 | ```yaml 183 | name: publish distributions 184 | 185 | on: 186 | workflow_dispatch: 187 | 188 | concurrency: 189 | group: ${{ github.workflow }}-${{ github.ref }} 190 | cancel-in-progress: true 191 | 192 | permissions: 193 | contents: read 194 | 195 | jobs: 196 | publish: 197 | name: Publish Python distribution to PyPI 198 | runs-on: ubuntu-latest 199 | permissions: 200 | id-token: write 201 | attestations: write 202 | environment: 203 | name: publish-package 204 | 205 | steps: 206 | # - name: Collect built artifacts 207 | # ... 208 | 209 | - name: Generate artifact attestation for sdist and wheels 210 | uses: actions/attest-build-provenance@ # vX.Y.Z 211 | with: 212 | subject-path: "dist/-*" 213 | 214 | - name: Verify artifact attestation 215 | env: 216 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 217 | shell: bash 218 | run: | 219 | for artifact in dist/*; do 220 | echo "# ${artifact}" 221 | gh attestation verify "${artifact}" --repo ${{ github.repository }} 222 | done 223 | 224 | - name: Publish distribution to PyPI 225 | uses: pypa/gh-action-pypi-publish@ # vX.Y.Z 226 | with: 227 | print-hash: true 228 | ``` 229 | 230 | ## Notes 231 | 232 | - [Concise Guide for Developing More Secure Software from the OpenSSF](https://best.openssf.org/Concise-Guide-for-Developing-More-Secure-Software) 233 | - [GitHub Blog: Introducing Artifact Attestations–now in public beta](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/) 234 | -------------------------------------------------------------------------------- /spec-0001/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC 1 — Lazy Loading of Submodules and Functions" 3 | number: 1 4 | date: 2020-12-17 5 | author: 6 | - "Stéfan van der Walt " 7 | - "Jon Crall " 8 | - "Dan Schult " 9 | - "Jarrod Millman " 10 | endorsed-by: 11 | - ipython 12 | - networkx 13 | - numpy 14 | - scikit-image 15 | - scikit-learn 16 | - scipy 17 | shortcutDepth: 3 18 | --- 19 | 20 | ## Description 21 | 22 | This SPEC recommends a lazy loading mechanism—targeted at libraries—that avoids import slowdowns 23 | and provides explicit submodule exports. 24 | 25 | For example, it allows the following behavior: 26 | 27 | ```python 28 | import skimage as ski # cheap operation; does not load submodules 29 | 30 | ski.filters # cheap operation; loads the filters submodule, but not 31 | # any of its submodules or functions 32 | 33 | ski.filters.gaussian(...) # loads the file in which gaussian is implemented 34 | # and calls that function 35 | ``` 36 | 37 | This has several advantages: 38 | 39 | 1. It exposes a **nested namespace that behaves as a flat namespace**. 40 | This avoids carefully having to import exactly the right combination of submodules, and allows interactive exploration of the namespace in an interactive terminal. 41 | 42 | 2. It **avoids having to optimize for import cost**. 43 | Currently, developers often move imports inside of functions to avoid slowing down importing their module. 44 | Lazy importing, when implemented through out a library, makes all imports cheap. 45 | 46 | 3. It provides **direct access to submodules**, avoiding local namespace conflicts. 47 | Instead of doing `import scipy.linalg as sla` to avoid clobbering a local `linalg`, one can now import each library and access its members directly: `import scipy; scipy.linalg`. 48 | 49 | ### Core Project Endorsement 50 | 51 | Endorsing this SPEC means agreeing, in principle, with the advantages of lazy loading described above. 52 | 53 | ### Ecosystem Adoption 54 | 55 | {{< admonition warning >}} 56 | We do not recommend lazy loading for all projects. E.g., small projects with low import overheads do not need it. Lazy loading is useful when you are concerned about subpackage import times, but also want to make those subpackages available for interactive exploration in, e.g., IPython. 57 | {{< /admonition >}} 58 | 59 | Adopting this SPEC means implementing, using the `lazy_loader` package or any other mechanism (such as module `__getattr__`), lazy loading of subpackages and, if desired, subpackage attributes. 60 | 61 | Lazy loading has been adopted by 62 | [scikit-image](https://github.com/scikit-image/scikit-image/pull/5101), 63 | [NetworkX](https://github.com/networkx/networkx/pull/4909), 64 | and [MNE-Python](https://github.com/mne-tools/mne-python/pull/11838). 65 | SciPy implements a [subset of lazy 66 | loading](https://github.com/scipy/scipy/pull/15230) which exposes only 67 | subpackages lazily. 68 | A prototype implementation of `lazy_loader` was adapted for 69 | [napari](https://github.com/napari/napari/pull/2816). 70 | 71 | #### Badges 72 | 73 | Projects can highlight their adoption of this SPEC by including a SPEC badge. 74 | {{< spec_badge number="1" title="Lazy Loading of Submodules and Functions" >}} 75 | To indicate adoption of multiple SPECS with one badge, see [this](../purpose-and-process/#badges). 76 | 77 | ## Implementation 78 | 79 | ### Background 80 | 81 | Early on, most scientific Python packages explicitly imported their submodules. 82 | For example, you would be able to do: 83 | 84 | ```python 85 | import scipy 86 | 87 | scipy.linalg.eig(...) 88 | ``` 89 | 90 | This was convenient: it had the simplicity of a flat namespace, but with the organization of a nested one. 91 | However, there was one drawback: importing submodules, especially large ones, introduced unacceptable slowdowns. 92 | 93 | For a while, SciPy had a lazy loading mechanism called `PackageLoader`. 94 | It was eventually dropped, because it failed frequently and in confusing ways—especially when used with interactive prompts. 95 | 96 | Thereafter, most libraries stopped importing submodules and relied on documentation to tell users which submodules to import. 97 | 98 | Commonly, code now reads: 99 | 100 | ```python 101 | from scipy import linalg 102 | linalg.eig(...) 103 | ``` 104 | 105 | Since the `linalg` submodule often conflicts with similar instances in other libraries, users also write: 106 | 107 | ```python 108 | # Invent an arbitrary name for each submodule 109 | import scipy.linalg as sla 110 | sla.eig(...) 111 | ``` 112 | 113 | or 114 | 115 | ```python 116 | # Import individual functions, making it harder to know where they are from 117 | # later on in code. 118 | from scipy.linalg import eig 119 | eig(...) 120 | ``` 121 | 122 | Python 3.7, with [PEP 562](https://www.python.org/dev/peps/pep-0562/), introduces the ability to override module `__getattr__` and `__dir__`. 123 | In combination, these features make it possible to again provide access to submodules, but without incurring performance penalties. 124 | 125 | ### `lazy_loader` 126 | 127 | To make it easier for projects to implement lazy loading of submodules and functions, we provide a utility 128 | library, called `lazy_loader`. It is implemented at 129 | https://github.com/scientific-python/lazy_loader and is 130 | installable from [pypi](https://pypi.org/project/lazy_loader/) and [conda-forge](https://anaconda.org/conda-forge/lazy_loader). 131 | 132 | #### Usage 133 | 134 | As an example, we will show how to set up lazy importing for `skimage.filters`. 135 | In the library's main `__init__.py`, specify which submodules are lazily loaded: 136 | 137 | ```python 138 | import lazy_loader as lazy 139 | 140 | submodules = [ 141 | ... 142 | 'filters', 143 | ... 144 | ] 145 | 146 | __getattr__, __dir__, _ = lazy.attach(__name__, submodules) 147 | ``` 148 | 149 | Then, in each submodule's `__init__.py` (in this case, `filters/__init__.py`), specify which functions are to be loaded from where: 150 | 151 | ```python 152 | import lazy_loader as lazy 153 | 154 | __getattr__, __dir__, __all__ = lazy.attach( 155 | __name__, 156 | submodules=['rank'] 157 | submod_attrs={ 158 | '_gaussian': ['gaussian', 'difference_of_gaussians'], 159 | 'edges': ['sobel', 'sobel_h', 'sobel_v', 160 | 'scharr', 'scharr_h', 'scharr_v', 161 | 'prewitt', 'prewitt_h', 'prewitt_v', 162 | 'roberts', 'roberts_pos_diag', 'roberts_neg_diag', 163 | 'laplace', 164 | 'farid', 'farid_h', 'farid_v'] 165 | } 166 | ) 167 | ``` 168 | 169 | The above would be equivalent to: 170 | 171 | ```python 172 | from . import rank 173 | from ._gaussian import gaussian, difference_of_gaussians 174 | from .edges import (sobel, sobel_h, sobel_v, 175 | scharr, scharr_h, scharr_v, 176 | prewitt, prewitt_h, prewitt_v, 177 | roberts, roberts_pos_diag, roberts_neg_diag, 178 | laplace, 179 | farid, farid_h, farid_v) 180 | ``` 181 | 182 | The difference being that the submodule is loaded only once it is accessed: 183 | 184 | ```python 185 | import skimage 186 | dir(skimage.filters) # This works as usual 187 | ``` 188 | 189 | Furthermore, the functions inside of the submodule are loaded only 190 | once they are needed: 191 | 192 | ```python 193 | import skimage 194 | 195 | skimage.filters.gaussian(...) # Lazy load `gaussian` from 196 | # `skimage.filters._gaussian` 197 | 198 | skimage.filters.rank.mean_bilateral(...) # Loaded once `rank` is accessed 199 | ``` 200 | 201 | One disadvantage is that erroneous or missing imports no longer fail immediately. 202 | During development and testing, the `EAGER_IMPORT` environment variable can be set to disable lazy loading, so that errors like these can be spotted. 203 | 204 | #### External libraries 205 | 206 | The `lazy_loader.attach` function is an alternative to setting up package internal imports. 207 | We also provide `lazy_loader.load` so that projects can lazily import external libraries: 208 | 209 | ```python 210 | linalg = lazy.load('scipy.linalg') # `linalg` will only be loaded when accessed 211 | ``` 212 | 213 | By default, import errors are postponed until usage. Import errors can 214 | be immediately raised with: 215 | 216 | ```python 217 | linalg = lazy.load('scipy.linalg', error_on_import=True) 218 | ``` 219 | 220 | #### Type checkers 221 | 222 | The lazy loading shown above has one drawback: static type checkers 223 | (such as [mypy](http://mypy-lang.org) and 224 | [pyright](https://github.com/microsoft/pyright)) will not be able to 225 | infer the types of lazy-loaded modules and functions. 226 | Therefore, `mypy` won't be able to detect potential errors, and 227 | integrated development environments such as [VS 228 | Code](https://code.visualstudio.com) won't provide code completion. 229 | 230 | To work around this limitation, we provide an alternative way to define lazy 231 | imports. 232 | Instead of importing modules and functions in `__init__.py` 233 | file with `lazy.attach`, you instead specify those imports in a `__init__.pyi` 234 | file—called a "type stub". 235 | Your `__init__.py` file then loads imports from the stub using `lazy.attach_stub`. 236 | 237 | Here's an example of how to convert this `__init__.py`: 238 | 239 | ```python 240 | # mypackage/__init__.py 241 | import lazy_loader as lazy 242 | 243 | __getattr__, __dir__, __all__ = lazy.attach( 244 | __name__, 245 | submod_attrs={ 246 | 'edges': ['sobel', 'sobel_h', 'sobel_v'] 247 | } 248 | ) 249 | ``` 250 | 251 | Add a [type stub (`__init__.pyi`) file](https://mypy.readthedocs.io/en/stable/stubs.html) in the same directory as the `__init__.py`. 252 | Type stubs are ignored at runtime, but used by static type checkers. 253 | 254 | ```python 255 | # mypackage/__init__.pyi 256 | from .edges import sobel as sobel, sobel_h as sobel_h, sobel_v as sobel_v 257 | ``` 258 | 259 | The explicit import naming `sobel as sobel` is [necessary due to PEP 484](https://github.com/microsoft/pyright/issues/3989#issuecomment-1260194688). 260 | Alternatively, you can manually provide an `__all__`: 261 | 262 | ```python 263 | # mypackage/__init__.pyi 264 | __all__ = ['sobel', 'sobel_h', 'sobel_v'] 265 | from .edges import sobel, sobel_h, sobel_v 266 | ``` 267 | 268 | Replace `lazy.attach` in `mypackage/__init__.py` with a call to `attach_stub`: 269 | 270 | ```python 271 | import lazy_loader as lazy 272 | 273 | # this assumes there is a `.pyi` file adjacent to this module 274 | __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__) 275 | ``` 276 | 277 | _Note that if you use a type stub, you will need to take additional action to add the `.pyi` file to your sdist and wheel distributions. 278 | See [PEP 561](https://peps.python.org/pep-0561/) and the [mypy documentation](https://mypy.readthedocs.io/en/stable/installed_packages.html#creating-pep-561-compatible-packages) for more information._ 279 | 280 | #### Caveats 281 | 282 | Thus far, we are aware of one corner case in which lazy loading does not work. 283 | This is when you define a lazily loaded function, say `my_func`, in a file of the same name (`my_func.py`) **AND** run doctests. 284 | Somehow, the doctest collector modifies the parent module's `__dict__` to include `my_func` (the module, not the function), essentially short circuiting the lazy loader and its ability to provide `my_module.my_func` (the function). 285 | Fortunately, there is an easy way to address this that already aligns with common practice: define `my_func` inside `_my_func.py` instead (note the underscore). 286 | 287 | #### YAML files 288 | 289 | Once a lazy import interface is implemented, other interesting options 290 | become available (but is not implemented in `lazy_loader`). 291 | For example, instead of specifying sub-submodules and functions the way we do above, one could do this in YAML files: 292 | 293 | ``` 294 | $ cat skimage/filters/init.yaml 295 | 296 | submodules: 297 | - rank 298 | 299 | functions: 300 | - _gaussian: 301 | - gaussian 302 | - difference_of_gaussians 303 | - edges: 304 | - sobel 305 | - sobel_h 306 | - sobel_v 307 | - scharr 308 | 309 | ... 310 | ``` 311 | 312 | Ultimately, we hope that lazy importing will become part of Python itself, but the developers have indicated that this is highly unlikely [^cannon]. 313 | In the mean time, we now have the necessary mechanisms to implement it ourselves. 314 | 315 | [^cannon]: Cannon B., personal communication, 7 January 2021. 316 | 317 | ## Notes 318 | 319 | - The [lazy loading blog post by Brett Cannon](https://snarky.ca/lazy-importing-in-python-3-7/) showed the feasibility of the concept, and informed our design. 320 | 321 | - Technical improvements happened around the [scikit-image PR](https://github.com/scikit-image/scikit-image/pull/5101) 322 | 323 | - [mkinit](https://github.com/Erotemic/mkinit) is a tool that automates the generation of `__init__.py` files, and supports this lazy loading mechanism. 324 | See, e.g., [NetworkX PR #4496](https://github.com/networkx/networkx/pull/4496). 325 | 326 | - The lazy loading discussion was initiated in [NetworkX PR #4401](https://github.com/networkx/networkx/pull/4401). 327 | -------------------------------------------------------------------------------- /purpose-and-process/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPEC Purpose and Process" 3 | date: 2020-12-17 4 | author: 5 | - "Jarrod Millman " 6 | - "Stéfan van der Walt " 7 | --- 8 | 9 | ## Description 10 | 11 | The SPEC process is designed to identify areas of shared concern between projects 12 | in the scientific Python ecosystem and to produce collaboratively written, 13 | community adopted guidelines for addressing these. 14 | Such guidelines are known as SPECs: Scientific Python Ecosystem Coordination documents. 15 | 16 | Specifically, the purpose of the SPEC process is 17 | 18 | 1. to help unify the ecosystem for users and developers; 19 | 2. to provide guidance to projects on technical issues or processes; 20 | 3. to document standard APIs, development tools, and community practices; and 21 | 4. to foster ecosystem-wide discussions of common problems and to develop shared solutions. 22 | 23 | Projects in the ecosystem have an existing, diverse set of proposal processes 24 | and development constraints. 25 | SPECs complement these: they are a mechanism to encourage shared practices and 26 | improve uniformity of experience across projects. 27 | SPECs may, for example, capture established practices so that new projects can 28 | learn from them; or they may propose a new practice that the authors believe 29 | will benefit the ecosystem as a whole. 30 | 31 | Projects decide for themselves whether to adopt any given SPEC—often, this 32 | would be through team consensus. 33 | They may look towards endorsements by [Core Projects](/specs/core-projects) as a signal to help them decide. 34 | A SPEC may not be a good fit for a project, and thus there is no 35 | expectation that all SPECs must be adopted by all projects (in fact, even Core Projects that endorse a SPEC—i.e., signaling that they think it is a good idea—may choose not to adopt it themselves). 36 | Still, SPECs best serve their purpose of communicating cross-project concepts when adopted by numerous projects—and their authority stems from the extent to which they are. 37 | 38 | Participants in the SPEC process must adhere to our 39 | [Code of Conduct](https://scientific-python.org/code_of_conduct/). 40 | 41 | ## What is a SPEC? 42 | 43 | A SPEC (Scientific Python Ecosystem Coordination) is a document that captures an idea applicable to multiple projects. 44 | It is the product of discussions with developers across the ecosystem, and captures one of the following types of narratives: 45 | 46 | - **Recommendation:** We recommend that you do Y (e.g., [SPEC 8 — Securing the Release Process](https://scientific-python.org/specs/spec-0008/)). 47 | - **Guideline:** Some projects may need to do Y. If you do Y, we recommend that you do it as follows (e.g., [SPEC 1 — Lazy Loading](https://scientific-python.org/specs/spec-0001/)). 48 | - **Memorandum:** If you do Y, you should be aware of the following (we don't have any such advisories yet). 49 | 50 | {{< admonition important >}} 51 | **What a SPEC is not** 52 | 53 | SPECs are _not_ meant to be extensive technical documents that cover a large amount of detail. 54 | They typically _summarize_ an idea, referring to external sources, such as GitHub repositories or websites, for further detail. 55 | 56 | If you find yourself wanting to _disseminate information_ across the 57 | ecosystem, it may be better to write a blog post on 58 | https://blog.scientific-python.org or to contribute to an existing 59 | piece of documentation, such as https://learn.scientific-python.org/development/. 60 | A blog post is also a good way to generate initial engagement around an idea that is not yet mature enough, or within scope, to become a SPEC. 61 | {{< /admonition >}} 62 | 63 | ### Key Terms 64 | 65 | Scientific Python Ecosystem 66 | : The **ecosystem** is a loose federation of community developed Python projects 67 | widely used in scientific research that interact well with one another and that 68 | follow similar norms of development, documentation, testing, and so forth. 69 | 70 | SPEC Core Projects 71 | : The [Core Projects](/specs/core-projects) 72 | are a small subset of the ecosystem consisting of mature, community developed projects 73 | that are (a) depended upon by most of the other projects and (b) responsible for 74 | reviewing, discussing, implementing, and endorsing SPEC documents. 75 | 76 | SPEC Steering Committee 77 | : The [Steering Committee](/specs/steering-committee) leads the SPEC project and 78 | manages the SPEC process including moderating 79 | the [SPECs discussion forum](https://discuss.scientific-python.org/c/specs/), 80 | accepting SPEC documents, and maintaining the SPEC process documents. 81 | 82 | SPEC Process 83 | : The SPEC process is outlined in this document and the associated 84 | [SPEC Steering Committee](/specs/steering-committee) and 85 | [SPEC Core Projects](/specs/core-projects) documents. 86 | This process is managed and overseen by the Steering Committee, and functions in collaboration 87 | with the Core Projects, community members, and projects across the ecosystem. 88 | 89 | SPEC Document 90 | : A **SPEC document** provides operational guidelines for projects and helps 91 | coordinate the ecosystem to provide a more unified experience for users and developers. 92 | These documents are developed collaboratively with the Core Projects and other interested 93 | ecosystem projects and community members. 94 | 95 | ### Format 96 | 97 | SPECs are UTF-8 encoded text files using 98 | [Markdown](https://www.markdownguide.org/) format and stored in the 99 | [SPEC repository](https://github.com/scientific-python/specs). 100 | The SPEC documents are converted to HTML by code in the 101 | [scientific-python.org repository](https://github.com/scientific-python/scientific-python.org/) using 102 | [Hugo](https://gohugo.io/) and deployed to 103 | [https://scientific-python.org/specs/](https://scientific-python.org/specs/). 104 | Each SPEC has two corresponding discussions: one under [SPECS → Ideas](https://discuss.scientific-python.org/c/specs/ideas), where the SPEC committee discussed its viability, and another under [SPECS → Web Comments](https://discuss.scientific-python.org/c/specs/web-comments), where public comments from https://scientific-python.org/specs are stored. 105 | 106 | ## Implementation 107 | 108 | The Steering Committee manages the SPEC process and will provide guidance to contributors 109 | throughout the process. 110 | In this section, we provide an overview of the main decision points in the SPEC process 111 | and provide guidance for how to get started with a new SPEC proposal. 112 | 113 | ### Decision Points 114 | 115 | A SPEC passes through four decision points over the course of 116 | its development and implementation: 117 | **Accept**, **Publish**, **Endorse**, and **Adopt**. 118 | 119 | 120 | ```mermaid 121 | graph LR 122 | 123 | START--> |Propose| A[Accept] 124 | A--> |Develop| B[Publish] 125 | B--> |Canvass| C[Endorse] 126 | C--> |Recommend| D[Adopt] 127 | 128 | click A callback "Steering Committee Action" 129 | click B callback "Author Action" 130 | click C callback "Core Project Action" 131 | click D callback "Ecosystem Action" 132 | 133 | ``` 134 | 135 | 136 | The authors start by _proposing_ a SPEC idea, as outlined in [New 137 | SPEC Proposals](#new-spec-proposals)—please read that section carefully before 138 | proposing a new SPEC. 139 | 140 | The decision to **accept** (and number) a SPEC is made by the Steering 141 | Committee, once there is agreement that the SPEC concept is 142 | applicable, and it has been confirmed that there are at least two 143 | authors from two different projects interested in working on the new 144 | SPEC and championing it to various projects. 145 | At this point, the authors may submit a first version of the SPEC as a 146 | PR to the [SPEC 147 | repository](https://github.com/scientific-python/specs). 148 | This version may be merged to the main branch whenever the authors 149 | consider it ready, clearly labeled as a draft (see `is-draft` header 150 | field). 151 | 152 | In the accepted phase, the authors _develop_ their SPEC, in 153 | consultation with Core Projects and interested community members. 154 | This is done in a collaborative and iterative process, focused on 155 | ensuring that the SPEC is broadly applicable and likely to be widely 156 | adopted. 157 | Once authors consider their SPEC complete, they **publish** it, 158 | removing its draft status. 159 | 160 | The author now _canvasses_ their project with the Core Projects, so 161 | they may **endorse** it. 162 | Once a SPEC is endorsed, substantive changes require the approval of 163 | all endorsing Core Projects. 164 | 165 | A SPEC is _recommended_ for wide-spread adoption once it is endorsed by 166 | two (or more) Core Projects. 167 | Additional details may be found in [Core Project 168 | documentation](/specs/core-projects). 169 | Individual projects choose to **adopt** the SPEC according to their 170 | own decision-making processes. 171 | Each SPEC describes what adopting it means in its _Ecosystem Adoption_ section. 172 | Ecosystem projects are welcome to adopt a SPEC at any point in the 173 | process, however, it may make sense to wait until a SPEC is endorsed 174 | by several Core Projects to ensure that the SPEC has been vetted and 175 | is deemed stable enough for widespread adoption. 176 | Once a SPEC is endorsed, it may still evolve, but the barrier for 177 | modifying the SPEC will increase substantially (since all endorsing 178 | projects would need to agree to changes). 179 | Projects that adopt a SPEC early should engage in the collaborative 180 | process leading to the SPEC being endorsed by the Core Projects, to 181 | ensure that their use cases are incorporated. 182 | 183 | #### Badges 184 | 185 | Projects can highlight their adoption of specific SPECs with a SPEC badge. 186 | For example, for SPEC 0, we recommend using 187 | 188 | {{< tabs >}} 189 | 190 | [[tab]] 191 | name = 'Rendered badge' 192 | content = ''' 193 | [![SPEC 0 — Minimum Supported Dependencies](https://img.shields.io/badge/SPEC-0-green?labelColor=%23004811&color=%235CA038)](https://scientific-python.org/specs/spec-0000/) 194 | ''' 195 | 196 | [[tab]] 197 | name = 'Markdown' 198 | content = ''' 199 | 200 | ``` 201 | [![SPEC 0 — Minimum Supported Dependencies](https://img.shields.io/badge/SPEC-0-green?labelColor=%23004811&color=%235CA038)](https://scientific-python.org/specs/spec-0000/) 202 | ``` 203 | 204 | ''' 205 | 206 | [[tab]] 207 | name = 'reStructuredText' 208 | content = ''' 209 | 210 | ``` 211 | |SPEC 0 — Minimum Supported Dependencies| 212 | 213 | .. |SPEC 0 — Minimum Supported Dependencies| image:: https://img.shields.io/badge/SPEC-0-green?labelColor=%23004811&color=%235CA038 214 | :target: https://scientific-python.org/specs/spec-0000/ 215 | ``` 216 | 217 | ''' 218 | 219 | {{< /tabs >}} 220 | 221 | Alternatively, you can use one badge to indicate adoption of multiple SPECs. 222 | For example, to indicate adoption of SPECs 0, 1, and 4, we recommend the following 223 | 224 | {{< tabs >}} 225 | 226 | [[tab]] 227 | name = 'Rendered badge' 228 | content = ''' 229 | [![Scientific Python Ecosystem Coordination](https://img.shields.io/badge/SPEC-0,1,4-green?labelColor=%23004811&color=%235CA038)](https://scientific-python.org/specs/) 230 | ''' 231 | 232 | [[tab]] 233 | name = 'Markdown' 234 | content = ''' 235 | 236 | ``` 237 | [![Scientific Python Ecosystem Coordination](https://img.shields.io/badge/SPEC-0,1,4-green?labelColor=%23004811&color=%235CA038)](https://scientific-python.org/specs/) 238 | ``` 239 | 240 | ''' 241 | 242 | [[tab]] 243 | name = 'reStructuredText' 244 | content = ''' 245 | 246 | ``` 247 | |Scientific Python Ecosystem Coordination| 248 | 249 | .. |Scientific Python Ecosystem Coordination| image:: https://img.shields.io/badge/SPEC-0,1,4-green?labelColor=%23004811&color=%235CA038 250 | :target: https://scientific-python.org/specs/ 251 | ``` 252 | 253 | ''' 254 | 255 | {{< /tabs >}} 256 | 257 | ### New SPEC Proposals 258 | 259 | 260 | 261 | A good SPEC proposal focuses on a single key recommendation or idea 262 | for coordinating projects in the scientific Python ecosystem, as 263 | discussed under [What is a SPEC?](#what-is-a-spec). 264 | 265 | As a SPEC moves through the process, it goes through different states, 266 | as discussed under [Decision Points](#decision-points) and summarized 267 | here. 268 | 269 | **Before proposing** a SPEC, we highly recommended that you first **vet 270 | the idea** by doing one or more of the following: 271 | 272 | 1. Discuss the idea with at least one project in the ecosystem—for example, by posting to a project mailing list, attending a community call, or by having a birds-of-a-feather discussion at a conference like SciPy; 273 | 2. discuss the idea with at least one other member of a [Core Project](/specs/core-projects); or 274 | 3. if it is a technical idea, create a minimal proof of concept. 275 | 276 | **Before submitting** a proposed SPEC: 277 | 278 | 1. Ensure that the SPEC has at least two authors from two different projects, 279 | to show cross-project interest. 280 | 281 | 2. The **idea must be proposed** on the discussion forum under the [`SPECS/Ideas` 282 | topic](https://discuss.scientific-python.org/c/specs/ideas/9). 283 | Please list your co-authors. 284 | 285 | If the SPEC committee considers the idea suitable for a SPEC, the spec 286 | is **approved** and a number is allocated. 287 | 288 | At this point, you should **draft your SPEC document and submit it** 289 | via pull request to the [SPEC repository](https://github.com/scientific-python/specs). 290 | 291 | Use the `quickstart.py` script to create the new SPEC document. 292 | Located at the top-level of the [SPEC 293 | repository](https://github.com/scientific-python/specs), the script 294 | will ask you a few questions[^newspec] and then create a new file 295 | appropriately named with a basic template for you to complete (e.g., 296 | `spec-0000/index.md`). 297 | Leave the `draft` field set to `true` and the `endorsed-by` field empty. 298 | Once the SPEC is in readable shape, file a pull request against the 299 | [SPEC repository](https://github.com/scientific-python/specs). 300 | Let the SPEC committee know when you are ready for your PR to be 301 | merged. 302 | Once they do so, the SPEC will appear in draft form at 303 | . 304 | 305 | Your job now is to refine the SPEC iteratively and collaboratively 306 | with the community, using follow-up PRs. 307 | You should focus on ensuring that the SPEC is broadly applicable and 308 | likely to be widely adopted. 309 | Once you consider your SPEC complete, **publish** it by making a PR to 310 | remove its draft status. 311 | 312 | ## Endorsing a SPEC 313 | 314 | 315 | 316 | [Core Projects](/specs/core-projects) may signal their approval of a SPEC by _endorsing_ it. 317 | This endorsement makes it more likely that other projects will _adopt_ it. 318 | Endorsing a SPEC does _not_, however, mean that a Core Project needs to _adopt_ a SPEC, although it typically would if feasible. 319 | Core Projects use their project-specific discussion and decision making mechanisms to decide whether to endorse a SPEC. 320 | 321 | Once a Core Project decides to endorse a SPEC, they add their project 322 | name to the `endorsed-by` field in the SPEC header via a pull request against 323 | the [scientific-python/specs](https://github.com/scientific-python/specs) 324 | repository. 325 | 326 | ## Notes 327 | 328 | [^newspec]: 329 | The script currently only supports adding one author. 330 | If you need to add additional authors, just edit the text file. 331 | 332 | Additional files associated with a SPEC document may be kept in the directory 333 | containing the SPEC. 334 | For example, files associated with `spec-0000/index.md` are in `spec-0000/`. 335 | --------------------------------------------------------------------------------