Shut is an opinionated tool to simplify publishing pure Python packages.

Overview

Welcome to Shut

Shut is an opinionated tool to simplify publishing pure Python packages.

What can Shut do for you?

  • Generate setup files (setup.py, MANIFEST.in, LICENSE.txt)
  • Sanity check your package configuration
  • Build and publish source/wheel distributions
  • Execute unit tests and static type checks
  • and more

Installation

Shut requires Python 3.7+ and can be installed from PyPI:

$ pip install shut

Quickstart

Initialize a new Python package:

$ shut pkg new --name my.package .
write .gitignore
write README.md
write package.yml
write src/my/package/__init__.py
write src/my/__init__.py

Generate setuptools files:

$ shut pkg update
write setup.py
write MANIFEST.in

Create a changelog entry and commit:

$ shut changelog --add feature --for cli -cm 'Added some useful options.'

Sanity-check the package configuration:

shut pkg checks

  ✔️   classifiers
  ⚠️   license: not specified
  ✔️   namespace files
  ✔️   package-author
  ⚠️   package-url: missing
  ✔️   package-version
  ✔️   readme
  ✔️   up to date

ran 8 checks for package my.package in 0.003s

Commit the current status:

$ git add . && git commit -m 'bootstrapped package'

Release a new version:

$ shut pkg bump --tag --push

figuring bump mode from changelog
  1 feature → minor

bumping 3 version reference(s)
  package.yml: 0.0.0 → 0.1.0
  setup.py: 0.0.0 → 0.1.0
  src/my/package/__init__.py: 0.0.0 → 0.1.0

release staged changelog
  .changelog/_unreleased.yml → .changelog/0.1.0.yml

updating files
  write setup.py
  write MANIFEST.in

tagging 0.1.0
[master ec1e9b3] (my.package) bump version to 0.1.0
 4 files changed, 4 insertions(+), 4 deletions(-)
 rename .changelog/{_unreleased.yml => 0.1.0.yml} (78%)
Enumerating objects: 24, done.
Counting objects: 100% (24/24), done.
Delta compression using up to 8 threads
Compressing objects: 100% (17/17), done.
Writing objects: 100% (24/24), 3.87 KiB | 566.00 KiB/s, done.
Total 24 (delta 4), reused 0 (delta 0)
To https://github.com/me/my-package
 * [new branch]      master -> master
 * [new tag]         0.1.0 -> 0.1.0

Publish the release to PyPI:

$ shut pkg publish warehouse:pypi

building setuptools:sdist
  :: build/my.package-0.1.0.tar.gz

building setuptools:wheel
  :: build/my.package-0.1.0-py3-none-any.whl

publishing warehouse:pypi
  :: build/my.package-0.1.0.tar.gz
  :: build/my.package-0.1.0-py3-none-any.whl

Shut also makes it easy to publish from within CI jobs. For more information on this, check out the Publishing Guide.


Copyright © 2021 Niklas Rosenstein

Comments
  • Make shut as a module

    Make shut as a module

    Shut cannot be run as python -m shut or coverage run -m shut pkg test

    When doing so:

    $> python -m shut
    /...bin/python: No module named shut.__main__; 'shut' is a package and cannot be directly executed
    $> coverage run -m shut pkg test --no-capture
    No module named shut.__main__; 'shut' is a package and cannot be directly executed
    

    This is an example of a __main__.py file (from pytest)

    https://github.com/pytest-dev/pytest/blob/main/src/pytest/main.py

    """The pytest entry point."""
    import pytest
    
    if __name__ == "__main__":    
        raise SystemExit(pytest.console_main())
    
    enhancement 
    opened by Mulugruntz 4
  • Copy of license file from parent folder in sdist doesn't work

    Copy of license file from parent folder in sdist doesn't work

    In a mono repository, a package can inherit the license file of the repo by temporarily copying it into the package folder during setup.py. This doesn't work anymore. Looking at databind.core==1.1.3's setup.py, I can see

    _tempcopy('../../LICENSE.txt', 'LICENSE.txt')
    

    I belive that is one ../ too many.

    bug 
    opened by NiklasRosenstein 2
  • Support  pytest xfail

    Support pytest xfail

    I have a parametrized test with marks=[pytest.mark.xfail] where I expect it to fail.

    However, I get an exception when running shut pkg test --no-capture:

    Traceback (most recent call last):
      File "/.../bin/shut", line 8, in <module>
        sys.exit(shut())
      File "/.../lib/python3.9/site-packages/click/core.py", line 829, in __call__
        return self.main(*args, **kwargs)
      File "/.../lib/python3.9/site-packages/click/core.py", line 782, in main
        rv = self.invoke(ctx)
      File "/.../lib/python3.9/site-packages/click/core.py", line 1259, in invoke
        return _process_result(sub_ctx.command.invoke(sub_ctx))
      File "/.../lib/python3.9/site-packages/click/core.py", line 1259, in invoke
        return _process_result(sub_ctx.command.invoke(sub_ctx))
      File "/.../lib/python3.9/site-packages/click/core.py", line 1066, in invoke
        return ctx.invoke(self.callback, **ctx.params)
      File "/.../lib/python3.9/site-packages/click/core.py", line 610, in invoke
        return callback(*args, **kwargs)
      File "/.../lib/python3.9/site-packages/shut/commands/pkg/test.py", line 153, in test
        test_run = test_package(package, isolate, keep_test_env, capture)
      File "/.../lib/python3.9/site-packages/shut/commands/pkg/test.py", line 70, in test_package
        return package.test_driver.test_package(package, runtime, capture)
      File "/.../lib/python3.9/site-packages/shut/test/pytest.py", line 126, in test_package
        test_run = load_report_file(self.report_file)
      File "/.../lib/python3.9/site-packages/shut/test/pytest.py", line 81, in load_report_file
        test_status = {'passed': TestStatus.PASSED, 'failed': TestStatus.FAILED, 'skipped': TestStatus.SKIPPED}[test['outcome']]
    KeyError: 'xfailed'
    

    Indeed, only passed, failed and skipped are acknowledged. Whereas xfailed and xpassed are not.

    Documentation: https://docs.pytest.org/en/6.2.x/example/parametrize.html

    bug 
    opened by Mulugruntz 2
  • shut versions 0.10.x may not have required `data` folder in package

    shut versions 0.10.x may not have required `data` folder in package

    shut versions 0.10.x may not have required data folder in package

    version 0.9.0

    user@computer:~/project$ 
    user@computer:~/project$ shut --version
    shut, version 0.9.0
    user@computer:~/project$ shut pkg checks
    
      ✔️   classifiers
      ✔️   license
      ✔️   namespace files
      ✔️   package-author
      ✔️   package-url
      ✔️   package-version
      ✔️   readme
      ✔️   up to date
    
    ran 8 checks for package project in 0.011s
    user@computer:~/project$ 
    

    version 0.10.3

    user@computer:~/project$ shut --version
    shut, version 0.10.3
    user@computer:~/project$ shut pkg checks
    /usr/lib/python3/dist-packages/apport/report.py:13: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
      import fnmatch, glob, traceback, errno, sys, atexit, locale, imp, stat
    Traceback (most recent call last):
      File "/home/user/.local/bin/shut", line 8, in <module>
        sys.exit(shut())
      File "/home/user/.local/lib/python3.8/site-packages/click/core.py", line 829, in __call__
        return self.main(*args, **kwargs)
      File "/home/user/.local/lib/python3.8/site-packages/click/core.py", line 782, in main
        rv = self.invoke(ctx)
      File "/home/user/.local/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
        return _process_result(sub_ctx.command.invoke(sub_ctx))
      File "/home/user/.local/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
        return _process_result(sub_ctx.command.invoke(sub_ctx))
      File "/home/user/.local/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
        return ctx.invoke(self.callback, **ctx.params)
      File "/home/user/.local/lib/python3.8/site-packages/click/core.py", line 610, in invoke
        return callback(*args, **kwargs)
      File "/home/user/.local/lib/python3.8/site-packages/shut/commands/pkg/checks.py", line 80, in checks
        sys.exit(check_package(package, warnings_as_errors))
      File "/home/user/.local/lib/python3.8/site-packages/shut/commands/pkg/checks.py", line 61, in check_package
        checks = get_package_checks(package)
      File "/home/user/.local/lib/python3.8/site-packages/shut/commands/pkg/checks.py", line 44, in get_package_checks
        checks = list(get_checks(package))
      File "/home/user/.local/lib/python3.8/site-packages/shut/checkers/base.py", line 125, in get_checks
        yield from checker().get_checks(obj)
      File "/home/user/.local/lib/python3.8/site-packages/shut/checkers/base.py", line 101, in get_checks
        for index, result in enumerate(value(subject)):
      File "/home/user/.local/lib/python3.8/site-packages/shut/checkers/package.py", line 137, in _check_up_to_date
        files = get_files(package)
      File "/home/user/.local/lib/python3.8/site-packages/shut/renderers/core.py", line 78, in get_files
        renderer().get_files(files, obj)
      File "/home/user/.local/lib/python3.8/site-packages/shut/renderers/license.py", line 81, in get_files
        get_license_template(model.license)
      File "/home/user/.local/lib/python3.8/site-packages/shut/renderers/license.py", line 34, in get_license_template
        return resource_string('shut', f'data/license_templates/{license_name}.txt').decode('utf-8')
      File "/home/user/.local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1155, in resource_string
        return get_provider(package_or_requirement).get_resource_string(
      File "/home/user/.local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1401, in get_resource_string
        return self._get(self._fn(self.module_path, resource_name))
      File "/home/user/.local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1626, in _get
        with open(path, 'rb') as stream:
    FileNotFoundError: [Errno 2] No such file or directory: '/home/user/.local/lib/python3.8/site-packages/shut/data/license_templates/MIT.txt'
    user@computer:~/project$ 
    user@computer:~/project$ ls -al /home/user/.local/lib/python3.8/site-packages/shut
    total 68
    drwxrwxr-x  16 user user 4096 Nov 30 13:29 .
    drwx------ 137 user user 4096 Nov 30 13:29 ..
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 builders
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 changelog
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 checkers
    drwxrwxr-x   7 user user 4096 Nov 30 13:29 commands
    -rw-rw-r--   1 user user 1205 Nov 30 13:29 __init__.py
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 lifecycle
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 lint
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 model
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 publish
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 publishers
    drwxrwxr-x   2 user user 4096 Nov 30 13:29 __pycache__
    -rw-rw-r--   1 user user    0 Nov 30 13:29 py.typed
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 renderers
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 test
    drwxrwxr-x   5 user user 4096 Nov 30 13:29 utils
    drwxrwxr-x   3 user user 4096 Nov 30 13:29 vcs
    user@computer:~/project$ 
    user@computer:~/project$ which shut
    /home/user/.local/bin/shut
    user@computer:~/project$
    
    
    bug 
    opened by ndejong 2
  • `ParseConstraintError` when parsing dependency without a constraint with `poetry-core>=1.2.0`

    `ParseConstraintError` when parsing dependency without a constraint with `poetry-core>=1.2.0`

    We're currently relying on the old behaviour that is fixed in https://github.com/python-poetry/poetry-core/pull/461 whereas an invalid constraint, such as an empty one, is treated as *. Now we have to be explicit about it.

    bug 
    opened by NiklasRosenstein 1
  • Support Poetry's table-format for dependency definition

    Support Poetry's table-format for dependency definition

    Poetry dependencies can be defined like

    [tool.poetry.dependencies]
    package1 = "^0.1.0"
    package2 = { version = "^0.1.0" }
    

    And currently, we only support the first format.

    The second format comes with the additional complexity of support different types of dependencies, sources (PyPI indices) and marking dependencies as optional.

    enhancement 
    opened by NiklasRosenstein 1
  • Loading project dependencies is a step that can be avoided for most Slap commands

    Loading project dependencies is a step that can be avoided for most Slap commands

    The project dependencies are loaded with every invocation of Slap right now because of how Project._get_projects() already orders the project list topologically. Most Slap commands don't care about the order (like slap release, etc.) and we can skip loading the dependencies if we instead create a separate API to retrieve the projects in topological order explicitly.

    enhancement 
    opened by NiklasRosenstein 1
  • `slap changelog update-pr` can't push the changes back to the repository if the Pull Request is coming from a forked repository

    `slap changelog update-pr` can't push the changes back to the repository if the Pull Request is coming from a forked repository

    1. The GitHub runner doesn't get an environment variable that tells us what the fork repository is
    2. We might not even have write permission on the forked repository from the GitHub runner anyway?
    opened by NiklasRosenstein 1
  • Execute test drivers in parallel

    Execute test drivers in parallel

    Having multiple independent test drivers could be run in parallel to improve response rate. Parallelism should be disabled when stdout capture is disabled.

    enhancement 
    opened by NiklasRosenstein 1
  • Validate `MANIFEST.in` content (each line matches at least one file) in `shut pkg checks`

    Validate `MANIFEST.in` content (each line matches at least one file) in `shut pkg checks`

    Lines that don't have any effect in MANIFEST.in do not cause an error or easily surface-able warning when building the package. The Shut package checks should validate that each line in the manifest serves a purpose. Lines that do not may be a mistake that needs to be corrected by the user.

    enhancement 
    opened by NiklasRosenstein 1
  • `standard file not found` when using lowercase or non-standard readme file in setuptools build

    `standard file not found` when using lowercase or non-standard readme file in setuptools build

    If you use readme.md, setuptools will complain that no README.{,rst,txt,md} file was found. The setup file we render passes the readme file content to setuptools.setup(), so not sure why this is an issue for setuptools.

    bug 
    opened by NiklasRosenstein 1
  • `ERROR: Double requirement given: `pytest>=6.0.0` (already in pytest, name='pytest')

    `ERROR: Double requirement given: `pytest>=6.0.0` (already in pytest, name='pytest')

    This occurs when creating a Python 3.8 virtual environment and using slap install to install kraken-core. It has an optional runtime-dependency on pytest and a non-optional development dependency on it.

    [tool.poetry.dependencies]
    pytest = { version = ">=6.0.0", optional = true }  # For the testing fixture provided by kraken.core
    
    [tool.poetry.dev-dependencies]
    pytest = "*"
    

    Upgrading Pip in the virtual environment makes it work.

    .venvs/3.8/bin/pip install --upgrade pip
    
    bug 
    opened by NiklasRosenstein 0
  • Consider adding `pyupio/safety` to the default `pyproject.toml` template

    Consider adding `pyupio/safety` to the default `pyproject.toml` template

    Consider adding pyupio/safety to the default pyproject.toml

    I'm finding that having safety as a part of my Slap tests helps me stay aware of possible issues with dependencies sooner in the development cycle, thus saving time (ie shift-left SecDevops language) - other Slap users may find this helpful too.

    [tool.slap.test]
    safety = "pip freeze | safety check --stdin --output bare"
    

    Also - totally love Slap and it's evolution from Slam and Shut - keep the tool going, it massively helps in delivering awesome Python packages.

    enhancement 
    opened by ndejong 2
  • `slap venv -i` errors in directory that has a `pyproject.toml` but cannot be managed by Slap

    `slap venv -i` errors in directory that has a `pyproject.toml` but cannot be managed by Slap

    The slap venv -i command is something you might run from ~/.profile or similar files, so it's not uncommon that it might be invoked in a directory that has a pyproject.toml but is not a compatible Slap project.

    Example error?

    [ ... ]
      File "/Users/niklas.rosenstein/gitme/slap/.venvs/3.10/lib/python3.10/site-packages/nr/util/functional/_once.py", line 29, in __call__
        self._value = self._supplier()
      File "/Users/niklas.rosenstein/gitme/slap/.venvs/3.10/lib/python3.10/site-packages/slap/project.py", line 150, in _get_dist_name
        return self.handler().get_dist_name(self)
      File "/Users/niklas.rosenstein/gitme/slap/.venvs/3.10/lib/python3.10/site-packages/nr/util/functional/_once.py", line 29, in __call__
        self._value = self._supplier()
      File "/Users/niklas.rosenstein/gitme/slap/.venvs/3.10/lib/python3.10/site-packages/slap/project.py", line 120, in _get_project_handler
        raise RuntimeError(f"unable to identify project handler for {self!r}")
    RuntimeError: unable to identify project handler for Project(directory="/Users/niklas.rosenstein/gitme/kraken-base-image")
    
    bug 
    opened by NiklasRosenstein 0
  • `slap venv` defaults to `python3` while `slap install` defaults to `python`

    `slap venv` defaults to `python3` while `slap install` defaults to `python`

    Using slap venv -c defaults topython3while theslap installcommand defaults topython. Usingpythonin the install command is OK because in a virtual environment it always points to the right environment. But probably we should haveslap venvusepython` by default.

    bug 
    opened by NiklasRosenstein 0
A plugin to simplify creating multi-page Dash apps

Multi-Page Dash App Plugin A plugin to simplify creating multi-page Dash apps. This is a preview of functionality that will of Dash 2.1. Background Th

Plotly 19 Dec 9, 2022
A thing to simplify listening for PG notifications with asyncpg

asyncpg-listen This library simplifies usage of listen/notify with asyncpg: Handles loss of a connection Simplifies notifications processing from mult

ANNA 18 Dec 23, 2022
Helper script to bootstrap a Python environment with the tools required to build and install packages.

python-bootstrap Helper script to bootstrap a Python environment with the tools required to build and install packages. Usage $ python -m bootstrap.bu

Filipe Laíns 7 Oct 6, 2022
A utility that makes it easy to work with Python projects containing lots of packages, of which you only want to develop some.

Mixed development source packages on top of stable constraints using pip mxdev [mɪks dɛv] is a utility that makes it easy to work with Python projects

BlueDynamics Alliance 6 Jun 8, 2022
Aurin - A quick AUR installer for Arch Linux. Install packages from AUR website in a click.

Aurin - A quick AUR installer for Arch Linux. Install packages from AUR website in a click.

Suleman 51 Nov 4, 2022
Python tool to check a web applications compliance with OWASP HTTP response headers best practices

Check Your Head A quick and easy way to check a web applications response headers!

Zak 6 Nov 9, 2021
Stubmaker is an easy-to-use tool for generating python stubs.

Stubmaker is an easy-to-use tool for generating python stubs. Requirements Stubmaker is to be run under Python 3.7.4+ No side effects during

Toloka 24 Aug 28, 2022
PyHook is an offensive API hooking tool written in python designed to catch various credentials within the API call.

PyHook is the python implementation of my SharpHook project, It uses various API hooks in order to give us the desired credentials. PyHook Uses

Ilan Kalendarov 158 Dec 22, 2022
Python based tool to extract forensic info from EventTranscript.db (Windows Diagnostic Data)

EventTranscriptParser EventTranscriptParser is python based tool to extract forensically useful details from EventTranscript.db (Windows Diagnostic Da

P. Abhiram Kumar 24 Nov 18, 2022
A tool written in python to generate basic repo files from github

A tool written in python to generate basic repo files from github

Riley 7 Dec 2, 2021
Simple Python tool that generates a pseudo-random password with numbers, letters, and special characters in accordance with password policy best practices.

Simple Python tool that generates a pseudo-random password with numbers, letters, and special characters in accordance with password policy best practices.

Joe Helle 7 Mar 25, 2022
SmarTool - Smart Util Tool for Python

A set of tools that keep Python sweeter.

Liu Tao 9 Sep 30, 2022
🦩 A Python tool to create comment-free Jupyter notebooks.

Pelikan Pelikan lets you convert notebooks to comment-free notebooks. In other words, It removes Python block and inline comments from source cells in

Hakan Özler 7 Nov 20, 2021
A simple tool to extract python code from a Jupyter notebook, and then run pylint on it for static analysis.

Jupyter Pylinter A simple tool to extract python code from a Jupyter notebook, and then run pylint on it for static analysis. If you find this tool us

Edmund Goodman 10 Oct 13, 2022
A python tool give n number of inputs and parallelly you will get a output by separetely

http-status-finder Hello Everyone!! This is kavisurya, In this tool you can give n number of inputs and parallelly you will get a output by separetely

KAVISURYA V 3 Dec 5, 2021
Daiho Tool is a Script Gathering for Windows/Linux systems written in Python.

Daiho is a Script Developed with Python3. It gathers a total of 22 Discord tools (including a RAT, a Raid Tool, a Nuker Tool, a Token Grabberr, etc). It has a pleasant and intuitive interface to facilitate the use of all with help and explanations for each of them.

AstraaDev 32 Jan 5, 2023
Minimal Windows system information tool written in Python

wfetch wfetch is a Minimal Windows system information tool written in Python (Only works on Windows) Installation First of all have python installed.

zJairO 3 Jan 24, 2022
A small python tool to get relevant values from SRI invoices

SriInvoiceProcessing A small python tool to get relevant values from SRI invoices Some useful info to run the tool Login into your SRI account and ret

Wladymir Brborich 2 Jan 7, 2022
SH-PUBLIC is a python based cloning script. You can clone unlimited UID facebook accounts by using this tool.

SH-PUBLIC is a python based cloning script. You can clone unlimited UID facebook accounts by using this tool. This tool works on any Android devices without root.

(Md. Tanvir Ahmed) 5 Mar 9, 2022