mass_driver


Namemass_driver JSON
Version 0.18.0 PyPI version JSON
download
home_pagehttps://github.com/OverkillGuy/mass-driver
SummarySend bulk repo change requests
upload_time2023-11-21 20:36:32
maintainer
docs_urlNone
authorJb Doyon
requires_python>=3.11,<4.0
licenseGPL-3.0-or-later
keywords repo-automation
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Mass Driver

![PyPI](https://img.shields.io/pypi/v/mass-driver)
![PyPI - License](https://img.shields.io/pypi/l/mass-driver)

Send bulk repo change requests.

This repository is on Github:
[Overkillguy/mass-driver](https://github.com/OverkillGuy/mass-driver/).

Requires Python 3.11.

See also the docs at [jiby.tech/mass-driver/](https://jiby.tech/mass-driver/)

## Installation

Install the package:

```none
pip install mass-driver
```

We recommend you install CLIs via [pipx](https://pypa.github.io/pipx/), for
dependency isolation:

```none
pipx install mass-driver
```

If you want to install from a git branch rather than Pypi:

```none
pipx install https://github.com/OverkillGuy/mass-driver
```

See pipx docs: <https://pypa.github.io/pipx/#running-from-source-control>

## Running the tool

Use the help menu to start with:

```none
mass-driver --help
```

## Preparing a change

Let's prepare for doing a change over dozens of repositories. We'll need to find
a `PatchDriver` that suits our needs, and configure it accordingly.

List available `PatchDriver`s via:

```none
mass-driver drivers --list
# The docs for a single driver:
mass-driver driver --info counter
```

Remember, `PatchDriver`s are exposed via a python plugin system, which means
anyone can package their own!

Once you've got a driver, you should create a Migration file, in TOML:

```toml
# Saved as "fix_teamname.toml"
[mass-driver.migration]
# As seen in 'git log':
commit_message = """Change team name

Team name XYZ is wrong, we should be called ABC instead.
See JIRA-123[1].

[1]: https://example.com/tickets/JIRA-123
"""

# Override the local git commit author
commit_author_name = "John Smith"
commit_author_email = "smith@example.com"

branch_name = "fix-team-name"

# PatchDriver class to use.
# Selected via plugin name, from "massdriver.drivers" entrypoint
driver_name = "teamname-changer"

# Config given to the PatchDriver instance
driver_config = { filename = "catalog.yaml", team_name = "Core Team" }

# Note: No "forge" section = no forge activity to pursue (no PR will be created)
```

With this file named `fix_teamname.toml` in hand, we can apply the change
locally, either against a local repo we've already cloned:

```shell
mass-driver run fix_teamname.toml --repo-path ~/workspace/my-repo/
```

Or against a repo being cloned from URL:

```shell
mass-driver run fix_teamname.toml --repo-path 'git@github.com:OverkillGuy/sphinx-needs-test.git'
```

The cloned repo will be under `.mass_driver/repos/USER/REPONAME/`.
We should expect a branch named `fix-team-name` with a single commit.

To apply the change over a list of repositories, create a file with relevant
repos:

```shell
cat <<EOF > repos.txt
git@github.com:OverkillGuy/sphinx-needs-test.git
git@github.com:OverkillGuy/speeders.git
EOF

mass-driver run fix_teamname.toml --repo-filelist repos.txt
```

## Creating PRs

Once the commits are done locally, let's send them up as PR a second step. For
this, we'll be creating a second activity file containing a Forge definition.

Similarly, forges can be listed and detailed:

```shell
mass-driver forges --list
# The docs for a single forge:
mass-driver forge --info counter
```

Consider using the `forge_name = "github"`.
Create a new Activity with a Forge:

``` toml
# An Activity made up of just a forge
[mass-driver.forge]
forge_name = "github"

base_branch = "main"

head_branch = "fix-teamname"
draft_pr = true
pr_title = "[JIRA-123] Bump counter.txt to 1"
pr_body = """Change team name

Team name XYZ is wrong, we should be called ABC instead.
See JIRA-123[1].

[1]: https://example.com/tickets/JIRA-123
"""

# Do you need to git push the branch before PR?
git_push_first = true
```

Now run mass-driver, remembering to set the `FORGE_TOKEN` envvar for a
Github/other auth token.

``` shell
export FORGE_TOKEN="ghp_supersecrettoken"
mass-driver run fix_teamname_forge.toml --repo-filelist repos.txt
```

## Combining migration then forge

Sometimes, we wish to expedite both the committing and the PR creation in a
single move.

The Activity file can contain both sections:

``` toml
# An activity made up of first a Migration, then a Forge
[mass-driver.migration]
# As seen in 'git log':
commit_message = """Change team name

Team name XYZ is wrong, we should be called ABC instead.
See JIRA-123[1].

[1]: https://example.com/tickets/JIRA-123
"""

# Override the local git commit author
commit_author_name = "John Smith"
commit_author_email = "smith@example.com"

branch_name = "fix-team-name"

# PatchDriver class to use.
# Selected via plugin name, from "massdriver.drivers" entrypoint
driver_name = "teamname-changer"

# Config given to the PatchDriver instance
driver_config = { filename = "catalog.yaml", team_name = "Core Team" }

# And a forge = PR creation after Migration
[mass-driver.forge]
forge_name = "github"

base_branch = "main"

head_branch = "fix-teamname"
draft_pr = true
pr_title = "[JIRA-123] Bump counter.txt to 1"
pr_body = """Change team name

Team name XYZ is wrong, we should be called ABC instead.
See JIRA-123[1].

[1]: https://example.com/tickets/JIRA-123
"""

# Do you need to git push the branch before PR?
git_push_first = true

```

## Discovering repos using a Source

Sometimes, the repos we want to apply patches to is a dynamic thing, coming from
tooling, like a Github repository search, some compliance tool report, service
catalogue, etc.

To address this, mass-driver can use a Source plugin to discover repositories to
apply activities to.

``` toml
# An Activity file with a file-list Source
[mass-driver.source]
source_name = "repo-list"
# Source 'repo-list' takes a 'repos' list of cloneable URLs:
[mass-driver.source.source_config]
repos = [
  "git@github.com:OverkillGuy/mass-driver.git",
  "git@github.com:OverkillGuy/mass-driver-plugins.git",
]
```

Because we included a `Source`, we can omit the CLI flags `--repo-path` or
`--repo-filelist`, to instead rely on the activity's config to discover the
repos.

``` shell
mass-driver run activity.toml
```

Smarter Sources can use more elaborate parameters, maybe even secret parameters
like API tokens.

Note that to pass secrets safely at runtime, config parameters passed via
`source_config` in file format can be passed as envvar, using prefix `SOURCE_`.
So we could have avoided the `repos` entry in file, by providing a
`SOURCE_REPOS` envvar instead. This feature works because the Source class
derives from `Pydantic.BaseSettings`.

As a `Source` developer, though, you should really look into usage of
`Pydantic.SecretStr` to avoid leaking the secret when config or result is
stored. See [Pydantic docs on Secret
fields](https://docs.pydantic.dev/1.10/usage/types/#secret-types).

## Using the scanners

Before doing any actual migration, we might want to explore existing
repositories to see what kind of change is required.

Mass-driver provides for this usecase via the scanners plugin system, enabling a
simple python function to be run against many repos, with the purpose of
gathering information.

<!-- scanner-activity -->
Let's define an Activity file specifying a list of scanners to run:

``` toml
# An Activity file for scanning
[mass-driver.scan]
scanner_names = ["root-files", "dockerfile-from"]
```

This can be run just like a migration:

``` shell
mass-driver run scan.toml --repo-filelist repos.txt
```

## Reviewing bulk PR status

Have a look at the `view-pr` subcommand for reviewing the status of many PRs at
once.

It requires specifying a forge like `github`, along with setting any required
tokens, such as via `FORGE_TOKEN` envvar for `github` forge.

``` shell
export FORGE_TOKEN=xyz
mass-driver view-pr github \
    --pr \
    https://github.com/OverkillGuy/mass-driver/pull/1 https://github.com/OverkillGuy/mass-driver/pull/2
# Can specify multiple PRs as a args list
```

Equivalently via a file full of newline-delimited PR URLs

``` shell
export FORGE_TOKEN=xyz
mass-driver view-pr github --pr-filelist prs.txt
```

With sample result:

```none
> Pull request review mode!
[001/004] Fetching PR status...
[002/004] Fetching PR status...
[003/004] Fetching PR status...
[004/004] Fetching PR status...

Merged:
https://github.com/OverkillGuy/mass-driver/pull/1
https://github.com/OverkillGuy/sphinx-needs-test/pull/1

Closed (but not merged):
https://github.com/OverkillGuy/mass-driver/pull/2
https://github.com/OverkillGuy/sphinx-needs-test/pull/2

In summary: 4 unique PRs, of which...
- 002 (50.0%) merged
- 002 (50.0%) closed (but not merged)
```

## Development

### Python setup

This repository uses Python3.11, using
[Poetry](https://python-poetry.org) as package manager to define a
Python package inside `src/mass_driver/`.

`poetry` will create virtual environments if needed, fetch
dependencies, and install them for development.

For ease of development, a `Makefile` is provided, use it like this:

``` shell
make  # equivalent to "make all" = install lint docs test build
# run only specific tasks:
make install
make lint
make test
# Combine tasks:
make install test
```

Once installed, the module's code can now be reached through running
Python in Poetry:

``` shell
$ poetry run python
>>> from mass_driver import main
>>> main("blabla")
```

This codebase uses [pre-commit](https://pre-commit.com) to run linting
tools like `ruff`. Use `pre-commit install` to install git
pre-commit hooks to force running these checks before any code can be
committed, use `make lint` to run these manually. Testing is provided
by `pytest` separately in `make test`.

### Documentation

Documentation is generated via [Sphinx](https://www.sphinx-doc.org/en/master/),
using the cool [myst_parser](https://myst-parser.readthedocs.io/en/latest/)
plugin to support Markdown files like this one.

Other Sphinx plugins provide extra documentation features, like the recent
[sphinx-autodoc2](https://sphinx-autodoc2.readthedocs.io/en/latest/index.html)
to generate API reference without headaches, and with myst-markdown support in
docstrings too!

To build the documentation, run

``` shell
# Requires the project dependencies provided by "make install"
make docs
# Generates docs/build/html/
```

To browse the website version of the documentation you just built, run:

``` shell
make docs-serve
```

And remember that `make` supports multiple targets, so you can generate the
documentation and serve it:

``` shell
make docs docs-serve
```

## License

This project is released under GPLv3 or later. See `COPYING` file for GPLv3
license details.

### Templated repository

This repository was created by the copier template available at
gh:OverkillGuy/python-template, using version v1.3.1.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/OverkillGuy/mass-driver",
    "name": "mass_driver",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.11,<4.0",
    "maintainer_email": "",
    "keywords": "repo-automation",
    "author": "Jb Doyon",
    "author_email": "jb@jiby.tech",
    "download_url": "https://files.pythonhosted.org/packages/01/d1/4f942d10549000c44f7bcea8690b1b84213fdcb3804dc3bb0bed0955527f/mass_driver-0.18.0.tar.gz",
    "platform": null,
    "description": "# Mass Driver\n\n![PyPI](https://img.shields.io/pypi/v/mass-driver)\n![PyPI - License](https://img.shields.io/pypi/l/mass-driver)\n\nSend bulk repo change requests.\n\nThis repository is on Github:\n[Overkillguy/mass-driver](https://github.com/OverkillGuy/mass-driver/).\n\nRequires Python 3.11.\n\nSee also the docs at [jiby.tech/mass-driver/](https://jiby.tech/mass-driver/)\n\n## Installation\n\nInstall the package:\n\n```none\npip install mass-driver\n```\n\nWe recommend you install CLIs via [pipx](https://pypa.github.io/pipx/), for\ndependency isolation:\n\n```none\npipx install mass-driver\n```\n\nIf you want to install from a git branch rather than Pypi:\n\n```none\npipx install https://github.com/OverkillGuy/mass-driver\n```\n\nSee pipx docs: <https://pypa.github.io/pipx/#running-from-source-control>\n\n## Running the tool\n\nUse the help menu to start with:\n\n```none\nmass-driver --help\n```\n\n## Preparing a change\n\nLet's prepare for doing a change over dozens of repositories. We'll need to find\na `PatchDriver` that suits our needs, and configure it accordingly.\n\nList available `PatchDriver`s via:\n\n```none\nmass-driver drivers --list\n# The docs for a single driver:\nmass-driver driver --info counter\n```\n\nRemember, `PatchDriver`s are exposed via a python plugin system, which means\nanyone can package their own!\n\nOnce you've got a driver, you should create a Migration file, in TOML:\n\n```toml\n# Saved as \"fix_teamname.toml\"\n[mass-driver.migration]\n# As seen in 'git log':\ncommit_message = \"\"\"Change team name\n\nTeam name XYZ is wrong, we should be called ABC instead.\nSee JIRA-123[1].\n\n[1]: https://example.com/tickets/JIRA-123\n\"\"\"\n\n# Override the local git commit author\ncommit_author_name = \"John Smith\"\ncommit_author_email = \"smith@example.com\"\n\nbranch_name = \"fix-team-name\"\n\n# PatchDriver class to use.\n# Selected via plugin name, from \"massdriver.drivers\" entrypoint\ndriver_name = \"teamname-changer\"\n\n# Config given to the PatchDriver instance\ndriver_config = { filename = \"catalog.yaml\", team_name = \"Core Team\" }\n\n# Note: No \"forge\" section = no forge activity to pursue (no PR will be created)\n```\n\nWith this file named `fix_teamname.toml` in hand, we can apply the change\nlocally, either against a local repo we've already cloned:\n\n```shell\nmass-driver run fix_teamname.toml --repo-path ~/workspace/my-repo/\n```\n\nOr against a repo being cloned from URL:\n\n```shell\nmass-driver run fix_teamname.toml --repo-path 'git@github.com:OverkillGuy/sphinx-needs-test.git'\n```\n\nThe cloned repo will be under `.mass_driver/repos/USER/REPONAME/`.\nWe should expect a branch named `fix-team-name` with a single commit.\n\nTo apply the change over a list of repositories, create a file with relevant\nrepos:\n\n```shell\ncat <<EOF > repos.txt\ngit@github.com:OverkillGuy/sphinx-needs-test.git\ngit@github.com:OverkillGuy/speeders.git\nEOF\n\nmass-driver run fix_teamname.toml --repo-filelist repos.txt\n```\n\n## Creating PRs\n\nOnce the commits are done locally, let's send them up as PR a second step. For\nthis, we'll be creating a second activity file containing a Forge definition.\n\nSimilarly, forges can be listed and detailed:\n\n```shell\nmass-driver forges --list\n# The docs for a single forge:\nmass-driver forge --info counter\n```\n\nConsider using the `forge_name = \"github\"`.\nCreate a new Activity with a Forge:\n\n``` toml\n# An Activity made up of just a forge\n[mass-driver.forge]\nforge_name = \"github\"\n\nbase_branch = \"main\"\n\nhead_branch = \"fix-teamname\"\ndraft_pr = true\npr_title = \"[JIRA-123] Bump counter.txt to 1\"\npr_body = \"\"\"Change team name\n\nTeam name XYZ is wrong, we should be called ABC instead.\nSee JIRA-123[1].\n\n[1]: https://example.com/tickets/JIRA-123\n\"\"\"\n\n# Do you need to git push the branch before PR?\ngit_push_first = true\n```\n\nNow run mass-driver, remembering to set the `FORGE_TOKEN` envvar for a\nGithub/other auth token.\n\n``` shell\nexport FORGE_TOKEN=\"ghp_supersecrettoken\"\nmass-driver run fix_teamname_forge.toml --repo-filelist repos.txt\n```\n\n## Combining migration then forge\n\nSometimes, we wish to expedite both the committing and the PR creation in a\nsingle move.\n\nThe Activity file can contain both sections:\n\n``` toml\n# An activity made up of first a Migration, then a Forge\n[mass-driver.migration]\n# As seen in 'git log':\ncommit_message = \"\"\"Change team name\n\nTeam name XYZ is wrong, we should be called ABC instead.\nSee JIRA-123[1].\n\n[1]: https://example.com/tickets/JIRA-123\n\"\"\"\n\n# Override the local git commit author\ncommit_author_name = \"John Smith\"\ncommit_author_email = \"smith@example.com\"\n\nbranch_name = \"fix-team-name\"\n\n# PatchDriver class to use.\n# Selected via plugin name, from \"massdriver.drivers\" entrypoint\ndriver_name = \"teamname-changer\"\n\n# Config given to the PatchDriver instance\ndriver_config = { filename = \"catalog.yaml\", team_name = \"Core Team\" }\n\n# And a forge = PR creation after Migration\n[mass-driver.forge]\nforge_name = \"github\"\n\nbase_branch = \"main\"\n\nhead_branch = \"fix-teamname\"\ndraft_pr = true\npr_title = \"[JIRA-123] Bump counter.txt to 1\"\npr_body = \"\"\"Change team name\n\nTeam name XYZ is wrong, we should be called ABC instead.\nSee JIRA-123[1].\n\n[1]: https://example.com/tickets/JIRA-123\n\"\"\"\n\n# Do you need to git push the branch before PR?\ngit_push_first = true\n\n```\n\n## Discovering repos using a Source\n\nSometimes, the repos we want to apply patches to is a dynamic thing, coming from\ntooling, like a Github repository search, some compliance tool report, service\ncatalogue, etc.\n\nTo address this, mass-driver can use a Source plugin to discover repositories to\napply activities to.\n\n``` toml\n# An Activity file with a file-list Source\n[mass-driver.source]\nsource_name = \"repo-list\"\n# Source 'repo-list' takes a 'repos' list of cloneable URLs:\n[mass-driver.source.source_config]\nrepos = [\n  \"git@github.com:OverkillGuy/mass-driver.git\",\n  \"git@github.com:OverkillGuy/mass-driver-plugins.git\",\n]\n```\n\nBecause we included a `Source`, we can omit the CLI flags `--repo-path` or\n`--repo-filelist`, to instead rely on the activity's config to discover the\nrepos.\n\n``` shell\nmass-driver run activity.toml\n```\n\nSmarter Sources can use more elaborate parameters, maybe even secret parameters\nlike API tokens.\n\nNote that to pass secrets safely at runtime, config parameters passed via\n`source_config` in file format can be passed as envvar, using prefix `SOURCE_`.\nSo we could have avoided the `repos` entry in file, by providing a\n`SOURCE_REPOS` envvar instead. This feature works because the Source class\nderives from `Pydantic.BaseSettings`.\n\nAs a `Source` developer, though, you should really look into usage of\n`Pydantic.SecretStr` to avoid leaking the secret when config or result is\nstored. See [Pydantic docs on Secret\nfields](https://docs.pydantic.dev/1.10/usage/types/#secret-types).\n\n## Using the scanners\n\nBefore doing any actual migration, we might want to explore existing\nrepositories to see what kind of change is required.\n\nMass-driver provides for this usecase via the scanners plugin system, enabling a\nsimple python function to be run against many repos, with the purpose of\ngathering information.\n\n<!-- scanner-activity -->\nLet's define an Activity file specifying a list of scanners to run:\n\n``` toml\n# An Activity file for scanning\n[mass-driver.scan]\nscanner_names = [\"root-files\", \"dockerfile-from\"]\n```\n\nThis can be run just like a migration:\n\n``` shell\nmass-driver run scan.toml --repo-filelist repos.txt\n```\n\n## Reviewing bulk PR status\n\nHave a look at the `view-pr` subcommand for reviewing the status of many PRs at\nonce.\n\nIt requires specifying a forge like `github`, along with setting any required\ntokens, such as via `FORGE_TOKEN` envvar for `github` forge.\n\n``` shell\nexport FORGE_TOKEN=xyz\nmass-driver view-pr github \\\n    --pr \\\n    https://github.com/OverkillGuy/mass-driver/pull/1 https://github.com/OverkillGuy/mass-driver/pull/2\n# Can specify multiple PRs as a args list\n```\n\nEquivalently via a file full of newline-delimited PR URLs\n\n``` shell\nexport FORGE_TOKEN=xyz\nmass-driver view-pr github --pr-filelist prs.txt\n```\n\nWith sample result:\n\n```none\n> Pull request review mode!\n[001/004] Fetching PR status...\n[002/004] Fetching PR status...\n[003/004] Fetching PR status...\n[004/004] Fetching PR status...\n\nMerged:\nhttps://github.com/OverkillGuy/mass-driver/pull/1\nhttps://github.com/OverkillGuy/sphinx-needs-test/pull/1\n\nClosed (but not merged):\nhttps://github.com/OverkillGuy/mass-driver/pull/2\nhttps://github.com/OverkillGuy/sphinx-needs-test/pull/2\n\nIn summary: 4 unique PRs, of which...\n- 002 (50.0%) merged\n- 002 (50.0%) closed (but not merged)\n```\n\n## Development\n\n### Python setup\n\nThis repository uses Python3.11, using\n[Poetry](https://python-poetry.org) as package manager to define a\nPython package inside `src/mass_driver/`.\n\n`poetry` will create virtual environments if needed, fetch\ndependencies, and install them for development.\n\nFor ease of development, a `Makefile` is provided, use it like this:\n\n``` shell\nmake  # equivalent to \"make all\" = install lint docs test build\n# run only specific tasks:\nmake install\nmake lint\nmake test\n# Combine tasks:\nmake install test\n```\n\nOnce installed, the module's code can now be reached through running\nPython in Poetry:\n\n``` shell\n$ poetry run python\n>>> from mass_driver import main\n>>> main(\"blabla\")\n```\n\nThis codebase uses [pre-commit](https://pre-commit.com) to run linting\ntools like `ruff`. Use `pre-commit install` to install git\npre-commit hooks to force running these checks before any code can be\ncommitted, use `make lint` to run these manually. Testing is provided\nby `pytest` separately in `make test`.\n\n### Documentation\n\nDocumentation is generated via [Sphinx](https://www.sphinx-doc.org/en/master/),\nusing the cool [myst_parser](https://myst-parser.readthedocs.io/en/latest/)\nplugin to support Markdown files like this one.\n\nOther Sphinx plugins provide extra documentation features, like the recent\n[sphinx-autodoc2](https://sphinx-autodoc2.readthedocs.io/en/latest/index.html)\nto generate API reference without headaches, and with myst-markdown support in\ndocstrings too!\n\nTo build the documentation, run\n\n``` shell\n# Requires the project dependencies provided by \"make install\"\nmake docs\n# Generates docs/build/html/\n```\n\nTo browse the website version of the documentation you just built, run:\n\n``` shell\nmake docs-serve\n```\n\nAnd remember that `make` supports multiple targets, so you can generate the\ndocumentation and serve it:\n\n``` shell\nmake docs docs-serve\n```\n\n## License\n\nThis project is released under GPLv3 or later. See `COPYING` file for GPLv3\nlicense details.\n\n### Templated repository\n\nThis repository was created by the copier template available at\ngh:OverkillGuy/python-template, using version v1.3.1.\n",
    "bugtrack_url": null,
    "license": "GPL-3.0-or-later",
    "summary": "Send bulk repo change requests",
    "version": "0.18.0",
    "project_urls": {
        "Documentation": "https://jiby.tech/mass-driver",
        "Homepage": "https://github.com/OverkillGuy/mass-driver",
        "Repository": "https://github.com/OverkillGuy/mass-driver"
    },
    "split_keywords": [
        "repo-automation"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "affaee4a50bf8cafa93db92222e3f6f2bdd4a8ca6069d67510253f65e518d494",
                "md5": "84424670dd638e70d7b56123697b63c8",
                "sha256": "0ffc8cc0f6de3d45ea4bbecbea7c90cf35988064885bbcb5f2a007a7eedcecb3"
            },
            "downloads": -1,
            "filename": "mass_driver-0.18.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "84424670dd638e70d7b56123697b63c8",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11,<4.0",
            "size": 65632,
            "upload_time": "2023-11-21T20:36:30",
            "upload_time_iso_8601": "2023-11-21T20:36:30.099468Z",
            "url": "https://files.pythonhosted.org/packages/af/fa/ee4a50bf8cafa93db92222e3f6f2bdd4a8ca6069d67510253f65e518d494/mass_driver-0.18.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "01d14f942d10549000c44f7bcea8690b1b84213fdcb3804dc3bb0bed0955527f",
                "md5": "af11adecb51a257873d867c819fd7793",
                "sha256": "c778a8ac8b9bf05d3af95694798acb663353bca69de6bcc1bb0436e415628504"
            },
            "downloads": -1,
            "filename": "mass_driver-0.18.0.tar.gz",
            "has_sig": false,
            "md5_digest": "af11adecb51a257873d867c819fd7793",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11,<4.0",
            "size": 40187,
            "upload_time": "2023-11-21T20:36:32",
            "upload_time_iso_8601": "2023-11-21T20:36:32.407224Z",
            "url": "https://files.pythonhosted.org/packages/01/d1/4f942d10549000c44f7bcea8690b1b84213fdcb3804dc3bb0bed0955527f/mass_driver-0.18.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-11-21 20:36:32",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "OverkillGuy",
    "github_project": "mass-driver",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "mass_driver"
}
        
Elapsed time: 0.60988s