Skip to content

Commit aa28405

Browse files
hugovkofek
andauthored
Add --dry-run to check how many bytes would be billed, but don't actually run query (#168)
Co-authored-by: Ofek Lev <ofekmeister@gmail.com>
1 parent ae5b78c commit aa28405

5 files changed

Lines changed: 52 additions & 34 deletions

File tree

README.md

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,36 +23,37 @@ pypinfo is a simple CLI to access [PyPI](https://pypi.org/) download statistics
2323
```console
2424
$ pypinfo
2525
Usage: pypinfo [OPTIONS] [PROJECT] [FIELDS]... COMMAND [ARGS]...
26-
27-
Valid fields are:
2826

29-
project | version | file | pyversion | percent3 | percent2 | impl | impl-version |
30-
31-
openssl | date | month | year | country | installer | installer-version |
32-
33-
setuptools-version | system | system-release | distro | distro-version | cpu |
27+
Valid fields are:
28+
29+
project | version | file | pyversion | percent3 | percent2 | impl | impl-version |
30+
31+
openssl | date | month | year | country | installer | installer-version |
32+
33+
setuptools-version | system | system-release | distro | distro-version | ci | cpu |
34+
35+
libc | libc-version
3436

35-
libc | libc-version
36-
3737
Options:
38-
-a, --auth TEXT Path to Google credentials JSON file.
39-
--run / --test --test simply prints the query.
40-
-j, --json Print data as JSON, with keys `rows` and `query`.
41-
-i, --indent INTEGER JSON indentation level.
42-
-t, --timeout INTEGER Milliseconds. Default: 120000 (2 minutes)
43-
-l, --limit INTEGER Maximum number of query results. Default: 10
44-
-d, --days INTEGER Number of days in the past to include. Default: 30
45-
-sd, --start-date TEXT Must be negative or YYYY-MM[-DD]. Default: -31
46-
-ed, --end-date TEXT Must be negative or YYYY-MM[-DD]. Default: -1
47-
-m, --month TEXT Shortcut for -sd & -ed for a single YYYY-MM month.
48-
-w, --where TEXT WHERE conditional. Default: file.project = "project"
49-
-o, --order TEXT Field to order by. Default: download_count
50-
--all Show downloads by all installers, not only pip.
51-
-pc, --percent Print percentages.
52-
-md, --markdown Output as Markdown.
53-
-v, --verbose Print debug messages to stderr.
54-
--version Show the version and exit.
55-
-h, --help Show this message and exit.
38+
-a, --auth TEXT Path to Google credentials JSON file.
39+
--run / --test --test simply prints the query.
40+
-n, --dry-run Don't run query but display how much data would be processed.
41+
-j, --json Print data as JSON, with keys `rows` and `query`.
42+
-i, --indent INTEGER JSON indentation level.
43+
-t, --timeout INTEGER Milliseconds. Default: 120000 (2 minutes)
44+
-l, --limit INTEGER Maximum number of query results. Default: 10
45+
-d, --days INTEGER Number of days in the past to include. Default: 30
46+
-sd, --start-date TEXT Must be negative or YYYY-MM[-DD]. Default: -31
47+
-ed, --end-date TEXT Must be negative or YYYY-MM[-DD]. Default: -1
48+
-m, --month TEXT Shortcut for -sd & -ed for a single YYYY-MM month.
49+
-w, --where TEXT WHERE conditional. Default: file.project = "project"
50+
-o, --order TEXT Field to order by. Default: download_count
51+
--all Show downloads by all installers, not only pip.
52+
-pc, --percent Print percentages.
53+
-md, --markdown Output as Markdown.
54+
-v, --verbose Print debug messages to stderr.
55+
--version Show the version and exit.
56+
-h, --help Show this message and exit.
5657
```
5758

5859
pypinfo accepts 0 or more options, followed by exactly 1 project, followed by

pypinfo/cli.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
@click.argument('fields', nargs=-1, required=False)
8383
@click.option('--auth', '-a', help='Path to Google credentials JSON file.')
8484
@click.option('--run/--test', default=True, help='--test simply prints the query.')
85+
@click.option('--dry-run', '-n', is_flag=True, help="Don't run query but display how much data would be processed.")
8586
@click.option('--json', '-j', is_flag=True, help='Print data as JSON, with keys `rows` and `query`.')
8687
@click.option('--indent', '-i', type=int, help='JSON indentation level.')
8788
@click.option('--timeout', '-t', type=int, default=120000, help='Milliseconds. Default: 120000 (2 minutes)')
@@ -104,6 +105,7 @@ def pypinfo(
104105
fields: list[str],
105106
auth: str,
106107
run: bool,
108+
dry_run: bool,
107109
json: bool,
108110
indent: int,
109111
timeout: int,
@@ -167,7 +169,7 @@ def pypinfo(
167169

168170
if run:
169171
with create_client(get_credentials()) as client:
170-
query_job = client.query(built_query, job_config=create_config())
172+
query_job = client.query(built_query, job_config=create_config(dry_run))
171173
query_rows = query_job.result(timeout=timeout // 1000)
172174
rows = parse_query_result(query_rows)
173175

@@ -187,12 +189,12 @@ def pypinfo(
187189
estimated_cost = Decimal(TIER_COST * billing_tier) / TB * Decimal(bytes_billed)
188190
estimated_cost_str = str(estimated_cost.quantize(TO_CENTS, rounding=ROUND_UP))
189191

190-
if len(rows) == 1 and not json:
192+
if len(rows) == 1 and not json and not dry_run:
191193
# Only headers returned
192194
click.echo("No data returned, check project name")
193195
return
194196

195-
if percent:
197+
if percent and not dry_run:
196198
rows = add_percentages(rows, include_sign=not json)
197199

198200
# Only for tables, and if more than the header row + a single data row
@@ -205,8 +207,9 @@ def pypinfo(
205207
click.echo(f'Data billed: {billed_amount:.2f} {billed_unit}')
206208
click.echo(f'Estimated cost: ${estimated_cost_str}')
207209

208-
click.echo()
209-
click.echo(tabulate(rows, markdown))
210+
if not dry_run:
211+
click.echo()
212+
click.echo(tabulate(rows, markdown))
210213
else:
211214
query_info = {
212215
'cached': from_cache,

pypinfo/core.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@
2626
Rows = list[list[str]]
2727

2828

29-
def create_config() -> QueryJobConfig:
29+
def create_config(dry_run: bool = False) -> QueryJobConfig:
3030
config = QueryJobConfig()
3131
config.use_legacy_sql = False
32+
if dry_run:
33+
config.dry_run = True
34+
config.use_query_cache = False
3235
return config
3336

3437

tests/test_core.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,19 @@ def test_create_config() -> None:
3333
config = core.create_config()
3434

3535
# Assert
36+
assert not config.dry_run
3637
assert not config.use_legacy_sql
3738

3839

40+
def test_create_config_dry_run() -> None:
41+
# Act
42+
config = core.create_config(dry_run=True)
43+
44+
# Assert
45+
assert config.dry_run
46+
assert not config.use_query_cache
47+
48+
3949
def test_normalize_dates_yyy_mm() -> None:
4050
# Arrange
4151
start_date = "2019-03"

tox.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ deps =
1818
commands =
1919
coverage run --parallel-mode -m pytest -W all {posargs}
2020
coverage combine --append
21-
coverage report -m
21+
coverage report --show-missing
22+
coverage html
2223
coverage xml
2324

2425
[testenv:lint]

0 commit comments

Comments
 (0)