{"id":32116772,"url":"https://github.com/scientific-python/spin","last_synced_at":"2026-01-16T08:08:17.545Z","repository":{"id":62980484,"uuid":"545712463","full_name":"scientific-python/spin","owner":"scientific-python","description":"Developer tool for scientific Python libraries","archived":false,"fork":false,"pushed_at":"2026-01-13T23:35:54.000Z","size":427,"stargazers_count":126,"open_issues_count":7,"forks_count":25,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-01-14T01:27:52.636Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scientific-python.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-10-04T21:15:38.000Z","updated_at":"2026-01-13T23:35:59.000Z","dependencies_parsed_at":"2024-03-02T03:29:28.507Z","dependency_job_id":"5e8e8766-8d7a-4b0f-baf3-a94df1d673b4","html_url":"https://github.com/scientific-python/spin","commit_stats":{"total_commits":199,"total_committers":11,"mean_commits":18.09090909090909,"dds":0.4170854271356784,"last_synced_commit":"1db0378afc42f6acb5a881672199890d01e4e576"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/scientific-python/spin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientific-python%2Fspin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientific-python%2Fspin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientific-python%2Fspin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientific-python%2Fspin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scientific-python","download_url":"https://codeload.github.com/scientific-python/spin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scientific-python%2Fspin/sbom","scorecard":{"id":804637,"data":{"date":"2025-08-11","repo":{"name":"github.com/scientific-python/spin","commit":"89e581c7201d0f6597ffc92c3e84894f99fc133b"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.2,"checks":[{"name":"Maintained","score":3,"reason":"2 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/label-check.yaml:1","Warn: no topLevel permission defined: .github/workflows/milestone-merged-prs.yaml:1","Warn: no topLevel permission defined: .github/workflows/release.yml:1","Info: topLevel 'contents' permission set to 'read': .github/workflows/test.yml:17","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":3,"reason":"Found 8/23 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":2,"reason":"dependency not pinned by hash detected -- score normalized to 2","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/scientific-python/spin/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/scientific-python/spin/release.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/scientific-python/spin/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/scientific-python/spin/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:28: update your workflow using https://app.stepsecurity.io/secureworkflow/scientific-python/spin/test.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/release.yml:29","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   1 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/release.yml:8"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: github.com/scientific-python/.github/SECURITY.md:1","Info: Found linked content: github.com/scientific-python/.github/SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: github.com/scientific-python/.github/SECURITY.md:1","Info: Found text in security policy: github.com/scientific-python/.github/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"SAST","score":2,"reason":"SAST tool is not run on all commits -- score normalized to 2","details":["Warn: 5 commits out of 24 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T11:26:39.505Z","repository_id":62980484,"created_at":"2025-08-23T11:26:39.505Z","updated_at":"2025-08-23T11:26:39.505Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-20T16:10:51.826Z","updated_at":"2026-01-16T08:08:17.375Z","avatar_url":"https://github.com/scientific-python.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 💫 Scientific Python INcantations (`spin`)\n\n## A developer tool for scientific Python libraries\n\nDevelopers need to memorize a whole bunch of magic command-line incantations.\nThese incantations may also change over time.\nOften, Makefiles are used to provide aliases, but Makefiles can be convoluted, are not written in Python, and are hard to extend.\nThe goal of `spin` is therefore to provide a simple, user-friendly, extendable interface for common development tasks.\nIt comes with a few common build commands out the box, but can easily be customized per project.\n\nAs a curiosity: the impetus behind developing the tool was the mass migration of scientific Python libraries (SciPy, scikit-image, and NumPy, etc.) to Meson, after distutils was deprecated.\nWhen many of the build and installation commands changed, it made sense to abstract away the nuisance of having to re-learn them.\n\n_Note:_ We now have experimental builds for editable installs.\nMost of the Meson commands listed below should work \"out of the box\" for those.\n\n\u003c!--TOC--\u003e\n\n- [Installation](#installation)\n- [Configuration](#configuration)\n  - [Command sections](#command-sections)\n- [Running](#running)\n- [Built-in commands](#built-in-commands)\n  - [Meson](#meson)\n  - [Build (PEP 517 builder)](#build-pep-517-builder)\n  - [pip (Package Installer for Python)](#pip-package-installer-for-python)\n  - [Meta (commands that operate on commands)](#meta-commands-that-operate-on-commands)\n- [🧪 Custom commands](#-custom-commands)\n  - [Configuration](#configuration-1)\n  - [Argument overrides](#argument-overrides)\n  - [Advanced: adding arguments to built-in commands](#advanced-adding-arguments-to-built-in-commands)\n  - [Advanced: override Meson CLI](#advanced-override-meson-cli)\n- [Auto-completion](#auto-completion)\n- [FAQ](#faq)\n- [For contributors](#for-contributors)\n- [History](#history)\n\n\u003c!--TOC--\u003e\n\n## Installation\n\n```\npip install spin\n```\n\n## Configuration\n\nSettings are stored in `.spin.toml`, `spin.toml`, or your project's `pyproject.toml`.\nAs an example, see the `[tool.spin]` section of [an example `pyproject.toml`](https://github.com/scientific-python/spin/blob/main/example_pkg/pyproject.toml).\n\nThe `[project]` section should contain `name`.\nThe `[tool.spin]` section should contain:\n\n```\npackage = \"pkg_importname\"  # name of your package\ncommands = [\n  \"spin.cmds.meson.build\",\n  \"spin.cmds.meson.test\"\n]\n```\n\nSee [the command selection](#built-in-commands) below.\n\n### Command sections\n\nOnce you have several commands, it may be useful to organize them into sections.\nIn `pyproject.toml`, instead of specifying the commands as a list, use the following structure:\n\n```toml\n[tool.spin.commands]\n\"Build\" = [\n  \"spin.cmds.meson.build\",\n  \"spin.cmds.meson.test\"\n]\n\"Environments\" = [\n  \"spin.cmds.meson.ipython\",\n  \"spin.cmds.meson.run\"\n]\n```\n\nThese commands will then be rendered as:\n\n```\nBuild:\n  build  🔧 Build package with Meson/ninja\n  test   🔧 Run tests\n\nEnvironments:\n  ipython  💻 Launch IPython shell with PYTHONPATH set\n  run      🏁 Run a shell command with PYTHONPATH set\n```\n\n## Running\n\n```\nspin\n```\n\nor\n\n```\npython -m spin\n```\n\n## Built-in commands\n\n### [Meson](https://meson-python.readthedocs.io)\n\nAvailable as `spin.cmds.meson.*`.\n\n```\nbuild      🔧 Build package with Meson/ninja\nipython    💻 Launch IPython shell with PYTHONPATH set\npython     🐍 Launch Python shell with PYTHONPATH set\nshell      💻 Launch shell with PYTHONPATH set\ntest       🔧 Run pytest\nrun        🏁 Run a shell command with PYTHONPATH set\ndocs       📖 Build Sphinx documentation\ngdb        👾 Execute a Python snippet with GDB\nlldb       👾 Execute a Python snippet with LLDB\n```\n\n### [Build](https://pypa-build.readthedocs.io/en/stable/) (PEP 517 builder)\n\nAvailable as `spin.cmds.build.*`:\n\n```\nsdist      📦 Build a source distribution in `dist/`\nwheel      📦 Build a wheel distribution in `dist/`\n```\n\n### [pip](https://pip.pypa.io) (Package Installer for Python)\n\n`pip` allows for editable installs, another common\ndevelopment workflow.\n\nAvailable as `spin.cmds.pip.*`:\n\n```\ninstall    💽 Build and install package using pip.\n```\n\n### Meta (commands that operate on commands)\n\nAvailable as `spin.cmds.meta.*`:\n\n```\nintrospect 🔍 Print a command's location and source code\n```\n\n## 🧪 Custom commands\n\n`spin` can invoke custom commands. These commands define their own arguments, and have access to the `pyproject.toml` file for further configuration.\n\nSee, e.g., the [example custom command](https://github.com/scientific-python/spin/blob/main/example_pkg/.spin/cmds.py).\n\nAdd custom commands to the `commands` variable in the `[tool.spin]` section of `pyproject.toml` as follows:\n\n```\ncommands = [..., '.spin/cmds.py:example']\n```\n\nHere, the command is stored in `.spin/cmds.py`, and the function\nis named `example`.\n\n### Configuration\n\nCustom commands can access the `pyproject.toml` as follows:\n\n```python\nfrom spin import util\n\n\n@click.command()\ndef example():\n    \"\"\"Command that accesses `pyproject.toml` configuration\"\"\"\n    config = util.get_config()\n    print(config[\"tool.spin\"])\n```\n\n### Argument overrides\n\nDefault arguments can be overridden for any command.\nThe custom command above, e.g., has the following signature:\n\n```python\n@click.command()\n@click.option(\"-f\", \"--flag\")\n@click.option(\"-t\", \"--test\", default=\"not set\")\ndef example(flag, test, default_kwd=None):\n    \"\"\"🧪 Example custom command.\n    ...\n    \"\"\"\n```\n\nUse the `[tool.spin.kwargs]` section to override default values for\nclick options or function keywords:\n\n```toml\n[tool.spin.kwargs]\n\".spin/cmds.py:example\" = {\"test\" = \"default override\", \"default_kwd\" = 3}\n```\n\n### Advanced: adding arguments to built-in commands\n\nInstead of rewriting a command from scratch, a project may simply want to add a flag to an existing `spin` command, or perhaps do some pre- or post-processing.\nFor this purpose, we provide the `spin.util.extend_cmd` decorator.\n\nHere, we show how to add a `--extra` flag to the existing `build` function:\n\n```python\nimport spin\n\n\n@click.option(\"-e\", \"--extra\", help=\"Extra test flag\")\n@spin.util.extend_command(spin.cmds.meson.build)\ndef build_extend(*, parent_callback, extra=None, **kwargs):\n    \"\"\"\n    This version of build also provides the EXTRA flag, that can be used\n    to specify an extra integer argument.\n    \"\"\"\n    print(f\"Preparing for build with {extra=}\")\n    parent_callback(**kwargs)\n    print(\"Finalizing build...\")\n```\n\nNote that `build_extend` receives the parent command callback (the function the `build` command would have executed) as its first argument.\n\nThe matching entry in `pyproject.toml` is:\n\n```\n\"Build\" = [\".spin/cmds.py:build_extend\"]\n```\n\nThe `extend_cmd` decorator also accepts a `doc` argument, for setting the new command's `--help` description.\nThe function documentation (\"This version of build...\") is also appended.\n\nFinally, `remove_args` is a tuple of arguments that are not inherited from the original command.\n\n### Advanced: override Meson CLI\n\nSome packages use a vendored version of Meson. The path to a custom\nMeson CLI can be set in `pyproject.toml`:\n\n```\n[tool.spin.meson]\ncli = 'path/to/custom/meson'\n```\n\n## Auto-completion\n\nTo enable shell auto-completion, first install `spin`, then follow these instructions\n(from the [click documentation](https://click.palletsprojects.com/en/stable/shell-completion/#enabling-completion)).\nThe same instructions work for ZSH, just replace \"bash\" with \"zsh\".\n\n1. Create a completions file:\n\n```\n_SPIN_COMPLETE=bash_source spin \u003e ~/.spin-complete.bash\n```\n\nIgnore the \"need valid configuration\" error messages.\n\n2. In your `~/.bashrc`, add:\n\n```\nsource ~/.spin-complete.bash\n```\n\nAuto-completions should now work in any spin-enabled project directory.\n\n## FAQ\n\n- Running `spin`, the emojis in the command list don't show up.\n\nYour terminal font may not include emoji characters. E.g., if you use\nnoto on Arch Linux the emojis are installed separately:\n\n```sh\nsudo pacman -S noto-fonts-emoji\nfc-cache -f -v\n```\n\n## For contributors\n\n`spin` development happens on GitHub at [scientific-python/spin](https://github.com/scientific-python/spin).\n`spin` tests are invoked using:\n\n```\nnox -s test\n```\n\nOther examples:\n\n```\nnox -s test -- -v\nnox -s test -- -v spin/tests/test_meson.py\n```\n\n`spin` takes a slightly more conservative approach than [SPEC 0](https://scientific-python.org/specs/spec-0000/), and\nsupports all non-EOL versions of Python.\n\n## History\n\nThe `dev.py` tool was [proposed for SciPy](https://github.com/scipy/scipy/issues/15489) by Ralf Gommers and [implemented](https://github.com/scipy/scipy/pull/15959) by Sayantika Banik, Eduardo Naufel Schettino, and Ralf Gommers (also see [Sayantika's blog post](https://labs.quansight.org/blog/the-evolution-of-the-scipy-developer-cli)).\nInspired by that implementation, `spin` (this package) is a minimal rewrite by Stéfan van der Walt, that aims to be easily extendable so that it can be used across ecosystem libraries.\nWe thank Danila Bredikhin and Luca Marconato who kindly donated the `spin` name on PyPi.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscientific-python%2Fspin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscientific-python%2Fspin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscientific-python%2Fspin/lists"}