Skip to content

Commit

Permalink
Add pep next command to find the next available number (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk authored Oct 10, 2023
2 parents 3ba404a + ff9f5bc commit eb1cf0f
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ exclude_lines =
# Don't complain if non-runnable code isn't run:
if __name__ == .__main__.:
def main
def _get_github_prs

if TYPE_CHECKING:
if not dry_run:

except ImportError:
except OSError:

[run]
omit =
Expand Down
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[flake8]
max_line_length = 88
max-line-length = 88
5 changes: 3 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,23 @@ repos:
hooks:
- id: flake8
additional_dependencies:
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging]

- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-check-blanket-noqa
- id: python-no-log-warn

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: check-json
- id: check-toml
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace

Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,20 @@ https://pep-previews--2440.org.readthedocs.build

<!-- [[[end]]] -->

### Find the next available PEP number

Check published PEPs and [open PRs](https://github.com/python/peps/pulls) to find the
next available PEP number.

<!-- [[[cog run("pep next") ]]] -->

```console
$ pep next
Next available PEP: 730
```

<!-- [[[end]]] -->

### Open a BPO issue in the browser

Issues from [bugs.python.org](https://bugs.python.org/) have been migrated to
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dynamic = [
"version",
]
dependencies = [
"ghapi",
"platformdirs",
"python-slugify",
"rapidfuzz",
Expand Down
83 changes: 75 additions & 8 deletions src/pepotron/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import importlib.metadata
import logging
from pathlib import Path
from typing import Any

from pepotron import _cache
from . import _cache

__version__ = importlib.metadata.version(__name__)

Expand Down Expand Up @@ -45,7 +46,7 @@

def _download_peps_json(json_url: str = BASE_URL + JSON_PATH) -> Path:
cache_file = _cache.filename(json_url)
logging.info(f"Cache file: {cache_file}")
logging.info("Cache file: %s", cache_file)

data = _cache.load(cache_file)
if data == {}:
Expand All @@ -56,7 +57,7 @@ def _download_peps_json(json_url: str = BASE_URL + JSON_PATH) -> Path:

# Raise if we made a bad request
# (4XX client error or 5XX server error response)
logging.info(f"HTTP status code: {resp.status}")
logging.info("HTTP status code: %s", resp.status)
if resp.status != 200:
msg = f"Unable to download {json_url}: status {resp.status}"
raise RuntimeError(msg)
Expand All @@ -69,15 +70,78 @@ def _download_peps_json(json_url: str = BASE_URL + JSON_PATH) -> Path:
return cache_file


def word_search(search: str | None) -> int:
def _get_peps() -> _cache.PepData:
import json

peps_file = _download_peps_json()

with open(peps_file) as f:
peps: _cache.PepData = json.load(f)

return peps


def _get_published_peps() -> set[int]:
peps = _get_peps()
numbers = {int(number) for number, details in peps.items()}
return numbers


def _next_available_pep() -> int:
try:
# Python 3.10+
from itertools import pairwise
except ImportError:
# Python 3.9 and below
def pairwise(iterable): # type: ignore
from itertools import tee

a, b = tee(iterable)
next(b, None)
return zip(a, b)

published = _get_published_peps()
proposed = _get_pr_peps()
combined = published | proposed
numbers = sorted(combined)

start = 400
next_pep = -1
for x, y in pairwise(numbers):
if x < start:
continue
if x + 1 != y:
next_pep = x + 1
break

return next_pep


def _get_github_prs() -> list[Any]:
from ghapi.all import GhApi # type: ignore

api = GhApi(owner="python", repo="peps", authenticate=False)
return api.pulls.list(per_page=100) # type: ignore[no-any-return]


def _get_pr_peps() -> set[int]:
import re

pr_title_regex = re.compile(r"^PEP (\d+): .*")

numbers = set()
for pr in _get_github_prs():
if match := re.search(pr_title_regex, pr.title):
number = match[1]
numbers.add(int(number))

return numbers


def word_search(search: str | None) -> int:
from rapidfuzz import process

with open(peps_file) as f:
peps = json.load(f)
peps = _get_peps()

# Dict of title->number
titles = {details["title"]: number for number, details in peps.items()}
Expand All @@ -89,11 +153,11 @@ def word_search(search: str | None) -> int:
print()

# Find PEP number of top match
number: int = next(
number: str = next(
number for number, details in peps.items() if details["title"] == result[0][0]
)

return number
return int(number)


def pep_url(search: str | None, base_url: str = BASE_URL, pr: int | None = None) -> str:
Expand All @@ -112,6 +176,9 @@ def pep_url(search: str | None, base_url: str = BASE_URL, pr: int | None = None)
if search.lower() in TOPICS:
return result + f"/topic/{search}/"

if search.lower() == "next":
return f"Next available PEP: {_next_available_pep()}"

try:
# pep 8
number = int(search)
Expand Down
2 changes: 1 addition & 1 deletion src/pepotron/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import atexit
import logging

from pepotron import BASE_URL, __version__, _cache, open_bpo, open_pep
from . import BASE_URL, __version__, _cache, open_bpo, open_pep

atexit.register(_cache.clear)

Expand Down
20 changes: 20 additions & 0 deletions tests/test_pepotron.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"""
from __future__ import annotations

from collections import namedtuple

import pytest

import pepotron
Expand All @@ -29,6 +31,24 @@ def test_url(search: str, expected_url: str) -> None:
assert pep_url == expected_url


def test_next() -> None:
# Arrange
Pull = namedtuple("Pull", ["title"])
prs = [
Pull(title="PEP 716: Seven One Six"),
Pull(title="PEP 717: Seven One Seven"),
]
# mock _get_github_prs:
pepotron._get_github_prs = lambda: prs

# Act
next_pep = pepotron.pep_url("next")

# Assert
assert next_pep.startswith("Next available PEP: ")
assert next_pep.split()[-1].isdigit()


@pytest.mark.parametrize(
"search, base_url, expected_url",
[
Expand Down
8 changes: 7 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ env_list =
extras =
tests
commands =
{envpython} -m pytest --cov pepotron --cov tests --cov-report html --cov-report term --cov-report xml {posargs}
{envpython} -m pytest \
--cov pepotron \
--cov tests \
--cov-report html \
--cov-report term \
--cov-report xml \
{posargs}
pep --version
pep --help

Expand Down

0 comments on commit eb1cf0f

Please sign in to comment.