# project


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

Before pullup can release a project it has to know what kind of project
it is, because each kind is released with different commands. This
module reads the marker files in a folder (`settings.ini`,
`pyproject.toml`, `Cargo.toml`, `setup_app.py`) and returns the steps
that release it. Nothing here runs a command or touches the network.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/project.py#L15"
target="_blank" style="float:right; font-size:smaller">source</a>

### Step

``` python
def Step(
    id:str, label:str, cmd:str, doc:str='', needs:list=<factory>, argv:list=<factory>, meta:dict=<factory>
)->None:
```

*One command in a release or deploy, with the keys it needs and the text
that describes it.*

A step is one command in a release, such as uploading to PyPI.
[`Step`](https://vedicreader.github.io/pullup/project.html#step) holds
what a user interface shows about it (`label` and `doc`), what to run
(`cmd`), and the environment keys the command needs (`needs`). Usually
`cmd` is the command itself and the pipeline splits it into arguments.
When a command would not split correctly, such as a `python -c` snippet
full of quotes and newlines, put the exact arguments in `argv` and keep
`cmd` as the text to display. `meta` carries anything else a caller
wants to keep with the step.

`dict` returns the step as a plain dictionary, with copies of its lists
so that changing the result leaves the step alone. A pipeline saves its
plan from these dictionaries and reads it back, `argv` included:

``` python
s = Step('pypi', 'PyPI release', 'python -m twine upload dist/*',
         'Upload the built distributions to PyPI.', ['TWINE_USERNAME', 'TWINE_PASSWORD'])
s.dict()
```

    {'id': 'pypi',
     'label': 'PyPI release',
     'cmd': 'python -m twine upload dist/*',
     'doc': 'Upload the built distributions to PyPI.',
     'needs': ['TWINE_USERNAME', 'TWINE_PASSWORD'],
     'argv': [],
     'meta': {}}

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/project.py#L60"
target="_blank" style="float:right; font-size:smaller">source</a>

### app_project

``` python
def app_project(
    root
):
```

*Whether the project also builds a desktop app, marked by `setup_app.py`
and `tools/build_release.py`.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/project.py#L56"
target="_blank" style="float:right; font-size:smaller">source</a>

### fastship_project

``` python
def fastship_project(
    root
):
```

*Whether `root` is a Python package with a `pyproject.toml` that is not
an nbdev project.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/project.py#L49"
target="_blank" style="float:right; font-size:smaller">source</a>

### maturin_project

``` python
def maturin_project(
    root
):
```

*Whether `root` is a Rust crate that maturin builds into a Python
wheel.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/project.py#L45"
target="_blank" style="float:right; font-size:smaller">source</a>

### rust_project

``` python
def rust_project(
    root
):
```

*Whether `root` is a Rust crate, meaning it has a `Cargo.toml`.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/project.py#L35"
target="_blank" style="float:right; font-size:smaller">source</a>

### nbdev_project

``` python
def nbdev_project(
    root
):
```

*Whether `root` is an nbdev project, by its nbdev 2 `settings.ini` or
its `pyproject.toml`.*

Each detector answers one yes-or-no question about a folder:

- [`nbdev_project`](https://vedicreader.github.io/pullup/project.html#nbdev_project):
  an nbdev library, declared by an nbdev 2 `settings.ini`, a
  `[tool.nbdev]` table in `pyproject.toml`, or an `nbdev` entry point
  there.
- [`rust_project`](https://vedicreader.github.io/pullup/project.html#rust_project):
  a Rust crate, which means there is a `Cargo.toml`.
- [`maturin_project`](https://vedicreader.github.io/pullup/project.html#maturin_project):
  a Rust crate that maturin also builds into a Python wheel, so its
  release goes to PyPI.
- [`fastship_project`](https://vedicreader.github.io/pullup/project.html#fastship_project):
  a Python package with a `pyproject.toml` that is not an nbdev library.
  fastship is the release tool for such packages.
- [`app_project`](https://vedicreader.github.io/pullup/project.html#app_project):
  a project that also builds a desktop app, recognised by `setup_app.py`
  together with `tools/build_release.py`.

A `pyproject.toml` that is missing or is not valid TOML counts as empty
instead of raising, so a half-written file never stops detection.

``` python
lib   = mkproj('a_notebook_lib', {'settings.ini': '[DEFAULT]\nlib_name = demo\nnbs_path = nbs\n'})
crate = mkproj('a_crate', {'Cargo.toml': '[package]\nname = "demo"\n'})
pkg   = mkproj('a_package', {'pyproject.toml': '[project]\nname = "demo"\n'})
[(p.name, nbdev_project(p), rust_project(p), fastship_project(p)) for p in (lib, crate, pkg)]
```

    [('a_notebook_lib', True, False, False),
     ('a_crate', False, True, False),
     ('a_package', False, False, True)]

Each kind of project has its own list of steps. `NBDEV_STEPS`,
`FASTSHIP_STEPS`, `PLAIN_STEPS`, `MATURIN_STEPS` and `CRATE_STEPS` are
those lists, and `FLOWS` maps a flow name to its list. Pass your own
mapping to
[`default_steps`](https://vedicreader.github.io/pullup/project.html#default_steps)
or
[`Project`](https://vedicreader.github.io/pullup/project.html#project)
to change what a flow runs.

`APP_STEPS` is not a flow of its own. Its two steps build the desktop
app and attach it to the GitHub release, and they are added to whichever
flow the project uses. A maturin release uploads nothing from your
machine: it pushes a tag, and CI builds and publishes a wheel for every
platform.

These are the step ids of each flow, then the keys the fastship steps
need:

``` python
{name: [s.id for s in steps] for name, steps in FLOWS.items()}
```

    {'nbdev': ['prepare', 'bump', 'gh', 'pypi'],
     'fastship': ['test', 'changelog', 'gh', 'pypi', 'bump'],
     'plain': ['test', 'build', 'gh', 'pypi'],
     'maturin': ['test', 'wheel', 'ci', 'changelog', 'release'],
     'crate': ['test', 'clippy', 'package', 'publish']}

``` python
{s.id: s.needs for s in FASTSHIP_STEPS if s.needs}
```

    {'changelog': ['GITHUB_TOKEN'],
     'gh': ['GITHUB_TOKEN'],
     'pypi': ['TWINE_USERNAME', 'TWINE_PASSWORD']}

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/project.py#L149"
target="_blank" style="float:right; font-size:smaller">source</a>

### default_steps

``` python
def default_steps(
    root, flows:NoneType=None
):
```

*The release steps for `root`, with the desktop-app steps added when the
project builds one.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/project.py#L136"
target="_blank" style="float:right; font-size:smaller">source</a>

### release_flow

``` python
def release_flow(
    root
):
```

*The name of the release flow that fits the project in `root`.*

[`release_flow`](https://vedicreader.github.io/pullup/project.html#release_flow)
names the flow for a folder. It checks for nbdev first, then for maturin
before a plain Rust crate (every maturin project is also a crate), then
for fastship. Anything else, including a folder that does not exist, is
`plain`.

[`default_steps`](https://vedicreader.github.io/pullup/project.html#default_steps)
returns that flow’s steps. For a project that builds a desktop app it
inserts the app steps straight after the last `gh` or `pypi` step, so
any step after those still runs last; a flow with neither gets them at
the end.

``` python
[(p.name, release_flow(p)) for p in (lib, crate, pkg, root/'no_such_folder')]
```

    [('a_notebook_lib', 'nbdev'),
     ('a_crate', 'crate'),
     ('a_package', 'fastship'),
     ('no_such_folder', 'plain')]

``` python
app = mkproj('a_desktop_app', {'pyproject.toml': '[project]\nname = "demo"\n',
                               'setup_app.py': '', 'tools/build_release.py': ''})
[s.id for s in default_steps(app)]
```

    ['test', 'changelog', 'gh', 'pypi', 'app', 'assets', 'bump']

The app steps land after `pypi` and before `bump`, because fastship
raises the version for the next cycle only once the release is out.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/project.py#L155"
target="_blank" style="float:right; font-size:smaller">source</a>

### Project

``` python
def Project(
    root, flows:NoneType=None
):
```

*The kind of project one folder holds and the steps that release it.*

[`Project`](https://vedicreader.github.io/pullup/project.html#project)
wraps all of this for one folder. It resolves the path once, when you
create it, but works out `kind` and `packages_an_app` again each time
you ask, so it notices files added since. `steps` returns the release
steps and `dict` returns everything as JSON-ready data for a user
interface.

``` python
p = Project(pkg)
d = p.dict()
p, d['kind'], d['app'], [s['id'] for s in d['steps']]
```

    (Project(a_package, fastship),
     'fastship',
     False,
     ['test', 'changelog', 'gh', 'pypi', 'bump'])
