# squawk [![npm](https://img.shields.io/npm/v/squawk-cli)](https://www.npmjs.com/package/squawk-cli)
> linter for Postgres migrations
[quick start](https://squawkhq.com/docs/) | [rules documentation](https://squawkhq.com/docs/rules) | [github action](https://github.com/sbdchd/squawk-action) | [diy github integration](https://squawkhq.com/docs/github_app)
## Why?
Prevent unexpected downtime caused by database migrations and encourage best
practices around Postgres schemas and SQL.
Also it seemed like a nice project to spend more time with Rust.
## Install
```shell
npm install -g squawk-cli
# or via PYPI
pip install squawk-cli
# or install binaries directly via the releases page
https://github.com/sbdchd/squawk/releases
```
## Usage
```shell
❯ squawk example.sql
example.sql:2:1: warning: prefer-text-field
2 | --
3 | -- Create model Bar
4 | --
5 | CREATE TABLE "core_bar" (
6 | "id" serial NOT NULL PRIMARY KEY,
7 | "alpha" varchar(100) NOT NULL
8 | );
note: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock.
help: Use a text field with a check constraint.
example.sql:9:2: warning: require-concurrent-index-creation
9 |
10 | CREATE INDEX "field_name_idx" ON "table_name" ("field_name");
note: Creating an index blocks writes.
note: Create the index CONCURRENTLY.
example.sql:11:2: warning: disallowed-unique-constraint
11 |
12 | ALTER TABLE table_name ADD CONSTRAINT field_name_constraint UNIQUE (field_name);
note: Adding a UNIQUE constraint requires an ACCESS EXCLUSIVE lock which blocks reads.
help: Create an index CONCURRENTLY and create the constraint using the index.
```
### `squawk --help`
```
squawk
Find problems in your SQL
USAGE:
squawk [FLAGS] [OPTIONS] [path]... [SUBCOMMAND]
FLAGS:
--assume-in-transaction
Assume that a transaction will wrap each SQL file when run by a migration tool
Use --no-assume-in-transaction to override this setting in any config file that exists
-h, --help
Prints help information
--list-rules
List all available rules
-V, --version
Prints version information
--verbose
Enable debug logging output
OPTIONS:
-c, --config <config-path>
Path to the squawk config file (.squawk.toml)
--dump-ast <ast-format>
Output AST in JSON [possible values: Raw, Parsed, Debug]
--exclude-path <excluded-path>...
Paths to exclude
For example: --exclude-path=005_user_ids.sql --exclude-path=009_account_emails.sql
--exclude-path='*user_ids.sql'
-e, --exclude <rule>...
Exclude specific warnings
For example: --exclude=require-concurrent-index-creation,ban-drop-database
--explain <rule>
Provide documentation on the given rule
--pg-version <pg-version>
Specify postgres version
For example: --pg-version=13.0
--reporter <reporter>
Style of error reporting [possible values: Tty, Gcc, Json]
--stdin-filepath <filepath>
Path to use in reporting for stdin
ARGS:
<path>...
Paths to search
SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
upload-to-github Comment on a PR with Squawk's results
```
## Rules
Individual rules can be disabled via the `--exclude` flag
```shell
squawk --exclude=adding-field-with-default,disallowed-unique-constraint example.sql
```
### Configuration file
Rules can also be disabled with a configuration file.
By default, Squawk will traverse up from the current directory to find a `.squawk.toml` configuration file. You may specify a custom path with the `-c` or `--config` flag.
```shell
squawk --config=~/.squawk.toml example.sql
```
The `--exclude` flag will always be prioritized over the configuration file.
**Example `.squawk.toml`**
```toml
excluded_rules = [
"require-concurrent-index-creation",
"require-concurrent-index-deletion",
]
```
See the [Squawk website](https://squawkhq.com/docs/rules) for documentation on each rule with examples and reasoning.
## Bot Setup
Squawk works as a CLI tool but can also create comments on GitHub Pull
Requests using the `upload-to-github` subcommand.
Here's an example comment created by `squawk` using the `example.sql` in the repo:
<https://github.com/sbdchd/squawk/pull/14#issuecomment-647009446>
See the ["GitHub Integration" docs](https://squawkhq.com/docs/github_app) for more information.
## `pre-commit` hook
Integrate Squawk into Git workflow with [pre-commit](https://pre-commit.com/). Add the following
to your project's `.pre-commit-config.yaml`:
```
repos:
- repo: https://github.com/sbdchd/squawk
rev: v0.10.0
hooks:
- id: squawk
files: path/to/postres/migrations/written/in/sql
```
Note the `files` parameter as it specifies the location of the files to be linted.
## prior art
- <https://github.com/erik/squabble>
### related tools
- <https://github.com/yandex/zero-downtime-migrations>
- <https://github.com/tbicr/django-pg-zero-downtime-migrations>
- <https://github.com/3YOURMIND/django-migration-linter>
- <https://github.com/ankane/strong_migrations>
- <https://github.com/AdmTal/PostgreSQL-Query-Lock-Explainer>
- <https://github.com/stripe/pg-schema-diff>
- <https://github.com/kristiandupont/schemalint>
## related blog posts / SE Posts / PG Docs
- <https://www.braintreepayments.com/blog/safe-operations-for-high-volume-postgresql/>
- <https://gocardless.com/blog/zero-downtime-postgres-migrations-the-hard-parts/>
- <https://www.citusdata.com/blog/2018/02/22/seven-tips-for-dealing-with-postgres-locks/>
- <https://realpython.com/create-django-index-without-downtime/#non-atomic-migrations>
- <https://dba.stackexchange.com/questions/158499/postgres-how-is-set-not-null-more-efficient-than-check-constraint>
- <https://www.postgresql.org/docs/10/sql-altertable.html#SQL-ALTERTABLE-NOTES>
- <https://www.postgresql.org/docs/current/explicit-locking.html>
- <https://benchling.engineering/move-fast-and-migrate-things-how-we-automated-migrations-in-postgres-d60aba0fc3d4>
- <https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680>
## dev
```shell
cargo install
cargo run
./s/test
./s/lint
./s/fmt
```
... or with nix:
```
$ nix develop
[nix-shell]$ cargo run
[nix-shell]$ cargo insta review
[nix-shell]$ ./s/test
[nix-shell]$ ./s/lint
[nix-shell]$ ./s/fmt
```
### adding a new rule
When adding a new rule, the `s/new-rule` script will create stubs for your rule in Rust and in Documentation site.
```bash
s/new-rule 'prefer big serial'
```
### releasing a new version
1. update the CHANGELOG.md and bump version in the cli `Cargo.toml`, ensure the
lock file is updated, and update `package.json` and commit the changes
```bash
# update version in Cargo.toml files and package.json to 4.5.3
s/update-version 4.5.3
```
2. create a new release on github - CI will attach the binaries automatically
3. wait for build artifacts to be attached to release.
4. login to `npm` and publish new version.
```bash
npm login
npm publish
```
### algolia
The squawkhq.com Algolia index can be found on [the crawler website](https://crawler.algolia.com/admin/crawlers/9bf0dffb-bc5a-4d46-9b8d-2f1197285213/overview). Algolia reindexes the site every day at 5:30 (UTC).
## how it works
squawk wraps calls to [libpg_query-sys](https://github.com/tdbgamer/libpg_query-sys) in a safe
interface and parses the JSON into easier to work with structures.
libpg_query-sys in turn uses [bindgen](https://rust-lang.github.io/rust-bindgen/) to bind to
[libpg_query](https://github.com/lfittl/libpg_query), which itself wraps Postgres' SQL
parser in a bit of C code that outputs the parsed AST into a JSON string.
Squawk then runs the rule functions over the parsed AST, gathers and pretty
prints the rule violations.
Raw data
{
"_id": null,
"home_page": null,
"name": "squawk-cli",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "postgres, postgresql, linter",
"author": "Steve Dignam <steve@dignam.xyz>",
"author_email": "Steve Dignam <steve@dignam.xyz>",
"download_url": "https://files.pythonhosted.org/packages/a1/9e/103b999c8cd231ba2896dc8aa887f5c6d010a179b988520426dedba1080d/squawk_cli-1.5.4.tar.gz",
"platform": null,
"description": "# squawk [![npm](https://img.shields.io/npm/v/squawk-cli)](https://www.npmjs.com/package/squawk-cli)\n\n> linter for Postgres migrations\n\n[quick start](https://squawkhq.com/docs/) | [rules documentation](https://squawkhq.com/docs/rules) | [github action](https://github.com/sbdchd/squawk-action) | [diy github integration](https://squawkhq.com/docs/github_app)\n\n## Why?\n\nPrevent unexpected downtime caused by database migrations and encourage best\npractices around Postgres schemas and SQL.\n\nAlso it seemed like a nice project to spend more time with Rust.\n\n## Install\n\n```shell\nnpm install -g squawk-cli\n\n# or via PYPI\npip install squawk-cli\n\n# or install binaries directly via the releases page\nhttps://github.com/sbdchd/squawk/releases\n```\n\n## Usage\n\n```shell\n\u276f squawk example.sql\nexample.sql:2:1: warning: prefer-text-field\n\n 2 | --\n 3 | -- Create model Bar\n 4 | --\n 5 | CREATE TABLE \"core_bar\" (\n 6 | \"id\" serial NOT NULL PRIMARY KEY,\n 7 | \"alpha\" varchar(100) NOT NULL\n 8 | );\n\n note: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock.\n help: Use a text field with a check constraint.\n\nexample.sql:9:2: warning: require-concurrent-index-creation\n\n 9 |\n 10 | CREATE INDEX \"field_name_idx\" ON \"table_name\" (\"field_name\");\n\n note: Creating an index blocks writes.\n note: Create the index CONCURRENTLY.\n\nexample.sql:11:2: warning: disallowed-unique-constraint\n\n 11 |\n 12 | ALTER TABLE table_name ADD CONSTRAINT field_name_constraint UNIQUE (field_name);\n\n note: Adding a UNIQUE constraint requires an ACCESS EXCLUSIVE lock which blocks reads.\n help: Create an index CONCURRENTLY and create the constraint using the index.\n```\n\n### `squawk --help`\n\n```\nsquawk\nFind problems in your SQL\n\nUSAGE:\n squawk [FLAGS] [OPTIONS] [path]... [SUBCOMMAND]\n\nFLAGS:\n --assume-in-transaction\n Assume that a transaction will wrap each SQL file when run by a migration tool\n\n Use --no-assume-in-transaction to override this setting in any config file that exists\n -h, --help\n Prints help information\n\n --list-rules\n List all available rules\n\n -V, --version\n Prints version information\n\n --verbose\n Enable debug logging output\n\n\nOPTIONS:\n -c, --config <config-path>\n Path to the squawk config file (.squawk.toml)\n\n --dump-ast <ast-format>\n Output AST in JSON [possible values: Raw, Parsed, Debug]\n\n --exclude-path <excluded-path>...\n Paths to exclude\n\n For example: --exclude-path=005_user_ids.sql --exclude-path=009_account_emails.sql\n\n --exclude-path='*user_ids.sql'\n\n -e, --exclude <rule>...\n Exclude specific warnings\n\n For example: --exclude=require-concurrent-index-creation,ban-drop-database\n --explain <rule>\n Provide documentation on the given rule\n\n --pg-version <pg-version>\n Specify postgres version\n\n For example: --pg-version=13.0\n --reporter <reporter>\n Style of error reporting [possible values: Tty, Gcc, Json]\n\n --stdin-filepath <filepath>\n Path to use in reporting for stdin\n\n\nARGS:\n <path>...\n Paths to search\n\n\nSUBCOMMANDS:\n help Prints this message or the help of the given subcommand(s)\n upload-to-github Comment on a PR with Squawk's results\n```\n\n## Rules\n\nIndividual rules can be disabled via the `--exclude` flag\n\n```shell\nsquawk --exclude=adding-field-with-default,disallowed-unique-constraint example.sql\n```\n\n### Configuration file\n\nRules can also be disabled with a configuration file.\n\nBy default, Squawk will traverse up from the current directory to find a `.squawk.toml` configuration file. You may specify a custom path with the `-c` or `--config` flag.\n\n```shell\nsquawk --config=~/.squawk.toml example.sql\n```\n\nThe `--exclude` flag will always be prioritized over the configuration file.\n\n**Example `.squawk.toml`**\n\n```toml\nexcluded_rules = [\n \"require-concurrent-index-creation\",\n \"require-concurrent-index-deletion\",\n]\n```\n\nSee the [Squawk website](https://squawkhq.com/docs/rules) for documentation on each rule with examples and reasoning.\n\n## Bot Setup\n\nSquawk works as a CLI tool but can also create comments on GitHub Pull\nRequests using the `upload-to-github` subcommand.\n\nHere's an example comment created by `squawk` using the `example.sql` in the repo:\n\n<https://github.com/sbdchd/squawk/pull/14#issuecomment-647009446>\n\nSee the [\"GitHub Integration\" docs](https://squawkhq.com/docs/github_app) for more information.\n\n## `pre-commit` hook\n\nIntegrate Squawk into Git workflow with [pre-commit](https://pre-commit.com/). Add the following\nto your project's `.pre-commit-config.yaml`:\n\n```\nrepos:\n - repo: https://github.com/sbdchd/squawk\n rev: v0.10.0\n hooks:\n - id: squawk\n files: path/to/postres/migrations/written/in/sql\n```\n\nNote the `files` parameter as it specifies the location of the files to be linted.\n\n## prior art\n\n- <https://github.com/erik/squabble>\n\n### related tools\n\n- <https://github.com/yandex/zero-downtime-migrations>\n- <https://github.com/tbicr/django-pg-zero-downtime-migrations>\n- <https://github.com/3YOURMIND/django-migration-linter>\n- <https://github.com/ankane/strong_migrations>\n- <https://github.com/AdmTal/PostgreSQL-Query-Lock-Explainer>\n- <https://github.com/stripe/pg-schema-diff>\n- <https://github.com/kristiandupont/schemalint>\n\n## related blog posts / SE Posts / PG Docs\n\n- <https://www.braintreepayments.com/blog/safe-operations-for-high-volume-postgresql/>\n- <https://gocardless.com/blog/zero-downtime-postgres-migrations-the-hard-parts/>\n- <https://www.citusdata.com/blog/2018/02/22/seven-tips-for-dealing-with-postgres-locks/>\n- <https://realpython.com/create-django-index-without-downtime/#non-atomic-migrations>\n- <https://dba.stackexchange.com/questions/158499/postgres-how-is-set-not-null-more-efficient-than-check-constraint>\n- <https://www.postgresql.org/docs/10/sql-altertable.html#SQL-ALTERTABLE-NOTES>\n- <https://www.postgresql.org/docs/current/explicit-locking.html>\n- <https://benchling.engineering/move-fast-and-migrate-things-how-we-automated-migrations-in-postgres-d60aba0fc3d4>\n- <https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680>\n\n## dev\n\n```shell\ncargo install\ncargo run\n./s/test\n./s/lint\n./s/fmt\n```\n\n... or with nix:\n\n```\n$ nix develop\n[nix-shell]$ cargo run\n[nix-shell]$ cargo insta review\n[nix-shell]$ ./s/test\n[nix-shell]$ ./s/lint\n[nix-shell]$ ./s/fmt\n```\n\n### adding a new rule\n\nWhen adding a new rule, the `s/new-rule` script will create stubs for your rule in Rust and in Documentation site.\n\n```bash\ns/new-rule 'prefer big serial'\n```\n\n### releasing a new version\n\n1. update the CHANGELOG.md and bump version in the cli `Cargo.toml`, ensure the\n lock file is updated, and update `package.json` and commit the changes\n\n ```bash\n # update version in Cargo.toml files and package.json to 4.5.3\n s/update-version 4.5.3\n ```\n\n2. create a new release on github - CI will attach the binaries automatically\n3. wait for build artifacts to be attached to release.\n4. login to `npm` and publish new version.\n\n ```bash\n npm login\n npm publish\n ```\n\n### algolia\n\nThe squawkhq.com Algolia index can be found on [the crawler website](https://crawler.algolia.com/admin/crawlers/9bf0dffb-bc5a-4d46-9b8d-2f1197285213/overview). Algolia reindexes the site every day at 5:30 (UTC).\n\n## how it works\n\nsquawk wraps calls to [libpg_query-sys](https://github.com/tdbgamer/libpg_query-sys) in a safe\ninterface and parses the JSON into easier to work with structures.\nlibpg_query-sys in turn uses [bindgen](https://rust-lang.github.io/rust-bindgen/) to bind to\n[libpg_query](https://github.com/lfittl/libpg_query), which itself wraps Postgres' SQL\nparser in a bit of C code that outputs the parsed AST into a JSON string.\n\nSquawk then runs the rule functions over the parsed AST, gathers and pretty\nprints the rule violations.\n\n",
"bugtrack_url": null,
"license": "GPL-3.0",
"summary": "Linter for PostgreSQL migrations",
"version": "1.5.4",
"project_urls": {
"Source Code": "https://github.com/sbdchd/squawk"
},
"split_keywords": [
"postgres",
" postgresql",
" linter"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "ae16f9e9d4f75dd8f017a175c6d98d0e56ad58151f825ed1a3b4ce846c0bf2c1",
"md5": "999ed87c3fb9557f0e78d3cd9562be5a",
"sha256": "1bf39c55a725e4bd35f8f83f3899be7df845dad79e8349d171d896d623bf3a89"
},
"downloads": -1,
"filename": "squawk_cli-1.5.4-py3-none-macosx_10_12_x86_64.whl",
"has_sig": false,
"md5_digest": "999ed87c3fb9557f0e78d3cd9562be5a",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 2924316,
"upload_time": "2025-01-16T23:18:22",
"upload_time_iso_8601": "2025-01-16T23:18:22.524859Z",
"url": "https://files.pythonhosted.org/packages/ae/16/f9e9d4f75dd8f017a175c6d98d0e56ad58151f825ed1a3b4ce846c0bf2c1/squawk_cli-1.5.4-py3-none-macosx_10_12_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "c54ac62bea98be96ec1679e85136464bf150fadea1af0ba5708e5a9fe09a3294",
"md5": "295911881a03bfe0637205fe06c689ce",
"sha256": "bcb09eeb36ee3619183abb8dd1eaddfd0aa971143008a2658b9ecd56a7c06484"
},
"downloads": -1,
"filename": "squawk_cli-1.5.4-py3-none-macosx_11_0_arm64.whl",
"has_sig": false,
"md5_digest": "295911881a03bfe0637205fe06c689ce",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 2839822,
"upload_time": "2025-01-16T23:18:20",
"upload_time_iso_8601": "2025-01-16T23:18:20.574575Z",
"url": "https://files.pythonhosted.org/packages/c5/4a/c62bea98be96ec1679e85136464bf150fadea1af0ba5708e5a9fe09a3294/squawk_cli-1.5.4-py3-none-macosx_11_0_arm64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "433a2e636f7067098c61562e6643c5f6d62ed24cdcb3d29f4632f8104867cb63",
"md5": "f92369839155d65c710e3f939407471e",
"sha256": "e3b34fe47957c4be137204cc5143e728add529d034ca783994b2c3c16fafc163"
},
"downloads": -1,
"filename": "squawk_cli-1.5.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"has_sig": false,
"md5_digest": "f92369839155d65c710e3f939407471e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 3555200,
"upload_time": "2025-01-16T23:18:14",
"upload_time_iso_8601": "2025-01-16T23:18:14.529033Z",
"url": "https://files.pythonhosted.org/packages/43/3a/2e636f7067098c61562e6643c5f6d62ed24cdcb3d29f4632f8104867cb63/squawk_cli-1.5.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "a51ec19c913b32fe230bac4932d9ee09203906ba2ecf5809c56796143afd1d93",
"md5": "b31090ca2ae126b5e9d02efb9db5da89",
"sha256": "2562e5604df778440acc016ec85caa67681a3ca6eebe4c34bd2dcecf655c288a"
},
"downloads": -1,
"filename": "squawk_cli-1.5.4-py3-none-manylinux_2_28_x86_64.whl",
"has_sig": false,
"md5_digest": "b31090ca2ae126b5e9d02efb9db5da89",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 4339487,
"upload_time": "2025-01-16T23:18:17",
"upload_time_iso_8601": "2025-01-16T23:18:17.487057Z",
"url": "https://files.pythonhosted.org/packages/a5/1e/c19c913b32fe230bac4932d9ee09203906ba2ecf5809c56796143afd1d93/squawk_cli-1.5.4-py3-none-manylinux_2_28_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "33347b665d5d45004227835262f71e27e91e07c3ef4eab5800aaeecf3519146f",
"md5": "4dedad965945d377ef9a2e4f421272a3",
"sha256": "5cd79313278bfe03f0646385540ff6233074bbb9ec8b98545e899fdca19d34ee"
},
"downloads": -1,
"filename": "squawk_cli-1.5.4-py3-none-win32.whl",
"has_sig": false,
"md5_digest": "4dedad965945d377ef9a2e4f421272a3",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 2474680,
"upload_time": "2025-01-16T23:18:27",
"upload_time_iso_8601": "2025-01-16T23:18:27.798737Z",
"url": "https://files.pythonhosted.org/packages/33/34/7b665d5d45004227835262f71e27e91e07c3ef4eab5800aaeecf3519146f/squawk_cli-1.5.4-py3-none-win32.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "53a28cf58b32258c11dace51fe934695ab80c71181c183eb98285642ed870f92",
"md5": "864025305c18cfff442a2c88e3e0120e",
"sha256": "0cd5e2e91f548f7b58071334ec129ee0c4841e1ef548274b8468aa06a56b094b"
},
"downloads": -1,
"filename": "squawk_cli-1.5.4-py3-none-win_amd64.whl",
"has_sig": false,
"md5_digest": "864025305c18cfff442a2c88e3e0120e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 2675367,
"upload_time": "2025-01-16T23:18:26",
"upload_time_iso_8601": "2025-01-16T23:18:26.276285Z",
"url": "https://files.pythonhosted.org/packages/53/a2/8cf58b32258c11dace51fe934695ab80c71181c183eb98285642ed870f92/squawk_cli-1.5.4-py3-none-win_amd64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "a19e103b999c8cd231ba2896dc8aa887f5c6d010a179b988520426dedba1080d",
"md5": "8cb6e166e04c689def3562633eb252ac",
"sha256": "454758f42dba1d7745f423380c341f3a3bee20285518ca6cde08508c1bef1d23"
},
"downloads": -1,
"filename": "squawk_cli-1.5.4.tar.gz",
"has_sig": false,
"md5_digest": "8cb6e166e04c689def3562633eb252ac",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 121324,
"upload_time": "2025-01-16T23:18:25",
"upload_time_iso_8601": "2025-01-16T23:18:25.153357Z",
"url": "https://files.pythonhosted.org/packages/a1/9e/103b999c8cd231ba2896dc8aa887f5c6d010a179b988520426dedba1080d/squawk_cli-1.5.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-01-16 23:18:25",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "sbdchd",
"github_project": "squawk",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "squawk-cli"
}