From 71fc5cf4e6af1ba3301fbc04317e3a91694b68a3 Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 10:28:25 +0100 Subject: [PATCH 01/27] Parse project topics --- jaraco/develop/git.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 8395d9e..ee90116 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -106,11 +106,13 @@ def path(self): class Project(str): """ - >>> p = Project.parse('foo-project [tag1] [tag2]') + >>> p = Project.parse('foo-project [tag1] [tag2] (zero defect, coherent software)') >>> p 'foo-project' >>> p.tags ['tag1', 'tag2'] + >>> p.topics + ['zero defect', 'coherent software'] """ pattern = re.compile(r'(?P\S+)\s*(?P.*)$') @@ -124,8 +126,11 @@ def __init__(self, value, **kwargs): @classmethod def parse(cls, line): match = types.SimpleNamespace(**cls.pattern.match(line).groupdict()) - tags = list(re.findall(r'\[(.*?)\]', match.rest)) - return cls(match.name, tags=tags) + tags = list(re.findall(r'\[(.*?)\]', rest := match.rest.rstrip())) + topics_assigned = re.match(r'[^\(\)]*\((.+)\)$', rest) + topics = topics_assigned and filter(None, topics_assigned.group(1).split(',')) + return cls(match.name, tags=tags, topics=list(map(str.strip, topics or ()))) + def resolve(name): From 59da2fa18aba58a62fbd5701f1c8d19decf1d54a Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 10:29:16 +0100 Subject: [PATCH 02/27] Allow to overwrite `temp_checkout()` depth --- jaraco/develop/git.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index ee90116..2f5e91d 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -213,7 +213,8 @@ def checkout_missing(project, root): @contextlib.contextmanager def temp_checkout(project, **kwargs): + kwargs.setdefault("depth", 50) with path.TempDir() as dir: - repo = checkout(project, dir, depth=50, **kwargs) + repo = checkout(project, dir, **kwargs) with repo: yield From c6d48dedc65f8e0871b4dc0d5aa4eb657b3e1d0c Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 10:38:39 +0100 Subject: [PATCH 03/27] Load PEP 566 summary in `get_project_metadata()` --- jaraco/develop/repo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jaraco/develop/repo.py b/jaraco/develop/repo.py index a5bf7bd..3183aa8 100644 --- a/jaraco/develop/repo.py +++ b/jaraco/develop/repo.py @@ -12,6 +12,7 @@ def get_project_metadata(): version = _md['Version'] project = urllib.parse.urlparse(url).path.strip('/') name = _md['Name'] + summary = _md.get('Summary') return types.SimpleNamespace(**locals()) From 01b07d4db8b569a25d990b14564d717079713bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Fri, 2 Feb 2024 10:23:03 +0100 Subject: [PATCH 04/27] Correct API base url for RTD --- jaraco/develop/rtd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jaraco/develop/rtd.py b/jaraco/develop/rtd.py index b4ade8c..50bdc2f 100644 --- a/jaraco/develop/rtd.py +++ b/jaraco/develop/rtd.py @@ -4,7 +4,7 @@ from requests_toolbelt import sessions -url = 'https://api.readthedocs.org/' +url = 'https://readthedocs.org/' @functools.lru_cache() From a9b3d7bd47271cbda77fbc57e12a7a2d2b74357b Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 10:48:49 +0100 Subject: [PATCH 05/27] Extend RTD facilities --- jaraco/develop/git.py | 7 +++++++ jaraco/develop/rtd.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 2f5e91d..e539c7c 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -131,6 +131,13 @@ def parse(cls, line): topics = topics_assigned and filter(None, topics_assigned.group(1).split(',')) return cls(match.name, tags=tags, topics=list(map(str.strip, topics or ()))) + @property + def rtd_slug(self): + return self.replace('.', '').replace('_', '-') + + @property + def rtd_url(self): + return f'https://{self.rtd_slug}.readthedocs.io/' def resolve(name): diff --git a/jaraco/develop/rtd.py b/jaraco/develop/rtd.py index 50bdc2f..c7eed8c 100644 --- a/jaraco/develop/rtd.py +++ b/jaraco/develop/rtd.py @@ -15,6 +15,12 @@ def session(): return session +def rtd_exists(project): + return session().head(f'projects/{project.rtd_slug}/').ok + + def enable_pr_build(project): - slug = project.replace('.', '').replace('_', '-') - session().patch(f'projects/{slug}/', data=dict(external_builds_enabled=True)) + session().patch( + f'projects/{project.rtd_slug}/', + data=dict(external_builds_enabled=True), + ) From 34d43d7085025a12c9a4b10b41a48d12abecc466 Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 10:49:52 +0100 Subject: [PATCH 06/27] Wrap GitHub repo metadata API --- jaraco/develop/github.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/jaraco/develop/github.py b/jaraco/develop/github.py index 51c8b34..766e245 100644 --- a/jaraco/develop/github.py +++ b/jaraco/develop/github.py @@ -94,6 +94,45 @@ def find_secrets(file): ) ) + def get_metadata(self): + """ + Get repository metadata. + + https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository + + >>> Repo('jaraco/pip-run').get_metadata()['description'] + 'pip-run - dynamic dependency loader for Python' + """ + resp = self.session.get(self) + resp.raise_for_status() + return resp.json() + + def update_metadata(self, **kwargs): + """ + Update repository metadata (without overwriting existing keys). + + Some useful metadata keys: + - name (str) + - description (str) + - homepage (str) + - visibility ("public" or "private") + - has_issues (bool) + - default_branch (str) + - archived (bool) + - allow_forking (bool) + + See docs for all of them: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#update-a-repository--parameters + + >>> Repo('jaraco/dead-parrot').update( + ... description="It's no more", + ... homepage='https://youtu.be/4vuW6tQ0218', + ... ) + + """ + resp = self.session.patch(self, json=kwargs) + resp.raise_for_status() + return resp + def username(): return os.environ.get('GITHUB_USERNAME') or getpass.getuser() From 2daab1b73d0406ef7815b540b2c1ece6a47f595f Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 10:50:11 +0100 Subject: [PATCH 07/27] Wrap GitHub topics API --- jaraco/develop/github.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/jaraco/develop/github.py b/jaraco/develop/github.py index 766e245..e554092 100644 --- a/jaraco/develop/github.py +++ b/jaraco/develop/github.py @@ -133,6 +133,27 @@ def update_metadata(self, **kwargs): resp.raise_for_status() return resp + def get_topics(self): + """ + Get topics for the repository. + + >>> Repo('jaraco/irc').get_topics() + ['irc', 'python'] + """ + resp = self.session.get(self + '/topics') + resp.raise_for_status() + return resp.json()['names'] + + def set_topics(self, *topics): + """Completely replace the existing topics with only the given ones.""" + resp = self.session.put(self + '/topics', json=dict(names=topics)) + resp.raise_for_status() + return resp + + def add_topics(self, *topics): + """Add new topics to the repository, without removing existing ones.""" + return self.set_topics(*self.get_topics(), *topics) + def username(): return os.environ.get('GITHUB_USERNAME') or getpass.getuser() From 8377a4cb352c833f785a399f9e90001e195f0205 Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 11:08:04 +0100 Subject: [PATCH 08/27] Sanitize topics in payload --- jaraco/develop/github.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jaraco/develop/github.py b/jaraco/develop/github.py index e554092..87253f7 100644 --- a/jaraco/develop/github.py +++ b/jaraco/develop/github.py @@ -146,7 +146,8 @@ def get_topics(self): def set_topics(self, *topics): """Completely replace the existing topics with only the given ones.""" - resp = self.session.put(self + '/topics', json=dict(names=topics)) + names = list(map(str.lower, (topic.replace(' ', '-') for topic in topics))) + resp = self.session.put(self + '/topics', json=dict(names=names)) resp.raise_for_status() return resp From 052e682667813443fcb0b92bba078cbcb2eb7c4d Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 11:36:43 +0100 Subject: [PATCH 09/27] First strip, then filter assigned topics It was possible to get empty strings as topics. --- jaraco/develop/git.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index e539c7c..fd1a2ba 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -128,8 +128,8 @@ def parse(cls, line): match = types.SimpleNamespace(**cls.pattern.match(line).groupdict()) tags = list(re.findall(r'\[(.*?)\]', rest := match.rest.rstrip())) topics_assigned = re.match(r'[^\(\)]*\((.+)\)$', rest) - topics = topics_assigned and filter(None, topics_assigned.group(1).split(',')) - return cls(match.name, tags=tags, topics=list(map(str.strip, topics or ()))) + topics = topics_assigned and map(str.strip, topics_assigned.group(1).split(',')) + return cls(match.name, tags=tags, topics=list(filter(None, topics or ()))) @property def rtd_slug(self): From 7c9b728260c1bba93040c813986c2d2577043eb4 Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 12:52:55 +0100 Subject: [PATCH 10/27] Make `jaraco.develop.github` import in `jaraco.develop.git` lazy --- jaraco/develop/git.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index fd1a2ba..65c83e7 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -12,8 +12,6 @@ import path from more_itertools import flatten -from . import github - class URLScheme: """ @@ -148,6 +146,7 @@ def resolve(name): >>> 'gh://pmxbot/pmxbot.nsfw' in projects True """ + from . import github default = URL(f'https://github.com/{github.username()}/') return default.join(name) From 37b7a723e63ca7721a33dc8e80d1767f8e4e72cb Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 12:56:49 +0100 Subject: [PATCH 11/27] Add `Repo.from_project()` constructor for correct initialization in forks --- jaraco/develop/github.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/jaraco/develop/github.py b/jaraco/develop/github.py index 87253f7..09a7826 100644 --- a/jaraco/develop/github.py +++ b/jaraco/develop/github.py @@ -4,6 +4,7 @@ import functools import re import pathlib +import posixpath import itertools import keyring @@ -11,6 +12,7 @@ import nacl.encoding from requests_toolbelt import sessions +from . import git from . import repo @@ -42,8 +44,14 @@ def load_token(): return token @classmethod - def detect(cls): - return cls(repo.get_project_metadata().project) + def from_project(cls, project, *, upstream=False): + if 'fork' in project.tags and not upstream: + return cls(posixpath.sep.join((username(), posixpath.basename(project)))) + return cls(git.resolve(project).path[1:]) + + @classmethod + def detect(cls, *, upstream=False): + return cls.from_project(repo.get_project_metadata().project, upstream=upstream) @functools.lru_cache() def get_public_key(self): From b8292933ecd94918290c93625328fc6297fa5a16 Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 12:57:25 +0100 Subject: [PATCH 12/27] Add `Repo.url` property --- jaraco/develop/github.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jaraco/develop/github.py b/jaraco/develop/github.py index 09a7826..414027f 100644 --- a/jaraco/develop/github.py +++ b/jaraco/develop/github.py @@ -24,6 +24,10 @@ class Repo(str): def __init__(self, name): self.session = self.get_session() + @property + def url(self): + return 'https://github.com/' + self + @classmethod @functools.lru_cache() def get_session(cls): From 3748dd20d1851ceaf212c6488c79f81d771d8b16 Mon Sep 17 00:00:00 2001 From: bswck Date: Fri, 2 Feb 2024 10:53:21 +0100 Subject: [PATCH 13/27] Create GitHub sync routine --- jaraco/develop/sync-github.py | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 jaraco/develop/sync-github.py diff --git a/jaraco/develop/sync-github.py b/jaraco/develop/sync-github.py new file mode 100644 index 0000000..8d04e24 --- /dev/null +++ b/jaraco/develop/sync-github.py @@ -0,0 +1,44 @@ +""" +Sync GitHub metadata repo across all projects. + +Metadata includes description, homepage (RTD docs if available) and topics. +""" + +import autocommand + +from . import filters +from . import git +from . import github +from . import repo +from . import rtd + + +@autocommand.autocommand(__name__) +def main( + keyword: filters.Keyword = None, # type: ignore + tag: filters.Tag = None, # type: ignore +): + for project in filter(tag, filter(keyword, git.projects())): + with git.temp_checkout(project, depth=1): + md = repo.get_project_metadata() + gh = github.Repo.from_project(project) + gh_metadata = gh.get_metadata() + # https://github.com/jaraco/pytest-perf/issues/10#issuecomment-1913669951 + # homepage = md.homepage + homepage = ( + gh_metadata.get('homepage') + or (project.rtd_url if rtd.rtd_exists(project) else None) + ) + description = gh_metadata.get('description') or md.summary + print(f'\n[Metadata for {gh}]') + print('Homepage:', homepage) + print('Description:', description) + print('Topics:', ', '.join(project.topics)) + gh.update_metadata( + homepage=homepage, + description=description, + ) + print('\nUpdated metadata.') + gh.add_topics(*project.topics) + print('Added topics.\n') + print(gh.url) From 07f02a9ef7ff0ccfb8a0a1e0c627b6d60de3e59f Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 3 Feb 2024 21:33:31 +0100 Subject: [PATCH 14/27] Guarantee `Project` objects always have `tags` and `topics` attributes --- jaraco/develop/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 65c83e7..93a7734 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -119,7 +119,7 @@ def __new__(self, value, **kwargs): return super().__new__(self, value) def __init__(self, value, **kwargs): - vars(self).update(kwargs) + vars(self).update({'tags': [], 'topics': [], **kwargs}) @classmethod def parse(cls, line): From 2bc6f4a5cca8fdd7568faf41dec4f27e8b278af7 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 3 Feb 2024 21:43:07 +0100 Subject: [PATCH 15/27] Use f-strings for uniform endpoint URL construction --- jaraco/develop/github.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jaraco/develop/github.py b/jaraco/develop/github.py index 414027f..8404275 100644 --- a/jaraco/develop/github.py +++ b/jaraco/develop/github.py @@ -26,7 +26,7 @@ def __init__(self, name): @property def url(self): - return 'https://github.com/' + self + return f'https://github.com/{self}' @classmethod @functools.lru_cache() @@ -152,14 +152,14 @@ def get_topics(self): >>> Repo('jaraco/irc').get_topics() ['irc', 'python'] """ - resp = self.session.get(self + '/topics') + resp = self.session.get(f'{self}/topics') resp.raise_for_status() return resp.json()['names'] def set_topics(self, *topics): """Completely replace the existing topics with only the given ones.""" names = list(map(str.lower, (topic.replace(' ', '-') for topic in topics))) - resp = self.session.put(self + '/topics', json=dict(names=names)) + resp = self.session.put(f'{self}/topics', json=dict(names=names)) resp.raise_for_status() return resp From ed12aa536023b4fc9cc53563adc97aa83832b5a2 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 3 Feb 2024 22:28:44 +0100 Subject: [PATCH 16/27] Use a factory constructor for `Project` --- jaraco/develop/git.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 93a7734..3ad9ce3 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -5,6 +5,7 @@ import posixpath import re import subprocess +import sys import types import urllib.parse @@ -114,9 +115,18 @@ class Project(str): """ pattern = re.compile(r'(?P\S+)\s*(?P.*)$') - - def __new__(self, value, **kwargs): - return super().__new__(self, value) + cache = {} + + def __new__(cls, value, **kwargs): + try: + return cls.cache[value] + except KeyError: + # Down-cast to a string early. + value = str(value) + sys.intern(value) # :) + new = super().__new__(cls, value) + cls.cache[new] = new + return new def __init__(self, value, **kwargs): vars(self).update({'tags': [], 'topics': [], **kwargs}) From 4f12601230f9d3e9988c0ef6368944f5aca0f474 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 3 Feb 2024 22:29:23 +0100 Subject: [PATCH 17/27] Implement `resolve_repo_name()` to find repository names --- jaraco/develop/git.py | 10 ++++++++++ jaraco/develop/github.py | 7 ++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 3ad9ce3..0dc2770 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -161,6 +161,16 @@ def resolve(name): return default.join(name) +def resolve_repo_name(name): + """ + >>> resolve_repo_name('keyring') + 'jaraco/keyring' + >>> resolve_repo_name('/pypa/setuptools') + 'pypa/setuptools' + """ + return resolve(name).path.removeprefix(posixpath.sep) + + def target_for_root(project, root: path.Path = path.Path()): """ Append the prefix of the resolved project name to the target diff --git a/jaraco/develop/github.py b/jaraco/develop/github.py index 8404275..d66857a 100644 --- a/jaraco/develop/github.py +++ b/jaraco/develop/github.py @@ -51,11 +51,12 @@ def load_token(): def from_project(cls, project, *, upstream=False): if 'fork' in project.tags and not upstream: return cls(posixpath.sep.join((username(), posixpath.basename(project)))) - return cls(git.resolve(project).path[1:]) + return cls(git.resolve_repo_name(project)) @classmethod def detect(cls, *, upstream=False): - return cls.from_project(repo.get_project_metadata().project, upstream=upstream) + project = git.Project(repo.get_project_metadata().project) + return cls.from_project(project, upstream=upstream) @functools.lru_cache() def get_public_key(self): @@ -135,7 +136,7 @@ def update_metadata(self, **kwargs): See docs for all of them: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#update-a-repository--parameters - >>> Repo('jaraco/dead-parrot').update( + >>> Repo('jaraco/dead-parrot').update_metadata( ... description="It's no more", ... homepage='https://youtu.be/4vuW6tQ0218', ... ) From 2ecc3b285cc07490df50805303ddf9065431b62c Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 3 Feb 2024 22:50:32 +0100 Subject: [PATCH 18/27] Use `Project.from_path` for project creation from metadata --- jaraco/develop/git.py | 8 ++++++++ jaraco/develop/github.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 0dc2770..873377f 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -139,6 +139,14 @@ def parse(cls, line): topics = topics_assigned and map(str.strip, topics_assigned.group(1).split(',')) return cls(match.name, tags=tags, topics=list(filter(None, topics or ()))) + @classmethod + def from_path(self, path): + from . import github + local = f'{github.username()}{posixpath.sep}' + if path.startswith(local): + return self(path.removeprefix(local)) + return self(posixpath.sep + path.removeprefix(posixpath.sep)) + @property def rtd_slug(self): return self.replace('.', '').replace('_', '-') diff --git a/jaraco/develop/github.py b/jaraco/develop/github.py index d66857a..fb8070b 100644 --- a/jaraco/develop/github.py +++ b/jaraco/develop/github.py @@ -55,7 +55,7 @@ def from_project(cls, project, *, upstream=False): @classmethod def detect(cls, *, upstream=False): - project = git.Project(repo.get_project_metadata().project) + project = git.Project.from_path(repo.get_project_metadata().project) return cls.from_project(project, upstream=upstream) @functools.lru_cache() From d08afd48e98c90991acf0b3dfe8de77a404ef899 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 3 Feb 2024 22:53:48 +0100 Subject: [PATCH 19/27] Don't send a PATCH request to a real repo while testing --- jaraco/develop/github.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jaraco/develop/github.py b/jaraco/develop/github.py index fb8070b..f202f8d 100644 --- a/jaraco/develop/github.py +++ b/jaraco/develop/github.py @@ -136,7 +136,7 @@ def update_metadata(self, **kwargs): See docs for all of them: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#update-a-repository--parameters - >>> Repo('jaraco/dead-parrot').update_metadata( + >>> Repo('jaraco/dead-parrot').update_metadata( # doctest: +SKIP ... description="It's no more", ... homepage='https://youtu.be/4vuW6tQ0218', ... ) From 6bf98dea27f37eaebb06ff0426880ca6202b9848 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 3 Feb 2024 23:44:08 +0100 Subject: [PATCH 20/27] Down-cast `Project` value arg before dict lookup --- jaraco/develop/git.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 873377f..649bf63 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -118,12 +118,11 @@ class Project(str): cache = {} def __new__(cls, value, **kwargs): + # Down-cast to a string early. + value = sys.intern(str(value)) try: return cls.cache[value] except KeyError: - # Down-cast to a string early. - value = str(value) - sys.intern(value) # :) new = super().__new__(cls, value) cls.cache[new] = new return new From 48cb955e00c5b0e694b53d915f2032c3d6d526ff Mon Sep 17 00:00:00 2001 From: bswck Date: Sun, 4 Feb 2024 02:10:25 +0100 Subject: [PATCH 21/27] Auto-use `git_url_substitutions` --- conftest.py | 2 +- jaraco/develop/git.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/conftest.py b/conftest.py index 1fa9dc8..0c4b5b2 100644 --- a/conftest.py +++ b/conftest.py @@ -8,7 +8,7 @@ def pytest_configure(): os.environ['GITHUB_TOKEN'] = 'abc' -@pytest.fixture +@pytest.fixture(autouse=True) def git_url_substitutions(fake_process): cmd = ['git', 'config', '--get-regexp', r'url\..*\.insteadof'] stdout = textwrap.dedent( diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 649bf63..81e3ab2 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -16,7 +16,6 @@ class URLScheme: """ - >>> getfixture('git_url_substitutions') >>> scheme = URLScheme.lookup('gh://foo/bar') >>> scheme.resolve('gh://foo/bar') 'https://github.com/foo/bar' From c755093b9216f4fe2680f17ec8040dc14fd92f58 Mon Sep 17 00:00:00 2001 From: bswck Date: Sun, 4 Feb 2024 02:10:55 +0100 Subject: [PATCH 22/27] Defer `github.Repo.detect()` execution to on demand --- jaraco/develop/add-github-secret.py | 6 ++++-- jaraco/develop/add-github-secrets.py | 5 ++++- jaraco/develop/create-github-release.py | 6 ++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/jaraco/develop/add-github-secret.py b/jaraco/develop/add-github-secret.py index 32acfcf..5ca90a7 100644 --- a/jaraco/develop/add-github-secret.py +++ b/jaraco/develop/add-github-secret.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import autocommand from . import github @autocommand.autocommand(__name__) -def run(name, value, project: github.Repo = github.Repo.detect()): - project.add_secret(name, value) +def run(name, value, project: github.Repo | None = None): + (project or github.Repo.detect()).add_secret(name, value) diff --git a/jaraco/develop/add-github-secrets.py b/jaraco/develop/add-github-secrets.py index c25914d..e39e170 100644 --- a/jaraco/develop/add-github-secrets.py +++ b/jaraco/develop/add-github-secrets.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import autocommand import keyring import getpass @@ -32,7 +34,8 @@ def _safe_getuser(): @autocommand.autocommand(__name__) -def run(project: github.Repo = github.Repo.detect()): +def run(project: github.Repo | None = None): + project = project or github.Repo.detect() for name in project.find_needed_secrets(): source = secret_sources[name] value = keyring.get_password(**source) diff --git a/jaraco/develop/create-github-release.py b/jaraco/develop/create-github-release.py index a4722c7..fd4d544 100644 --- a/jaraco/develop/create-github-release.py +++ b/jaraco/develop/create-github-release.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import autocommand from . import github @@ -5,6 +7,6 @@ @autocommand.autocommand(__name__) -def run(project: github.Repo = github.Repo.detect()): +def run(project: github.Repo | None = None): md = repo.get_project_metadata() - project.create_release(tag=f'v{md.version}') + (project or github.Repo.detect()).create_release(tag=f'v{md.version}') From ff42c34ea1f169f908da100f020a3f394effc305 Mon Sep 17 00:00:00 2001 From: bswck Date: Sun, 4 Feb 2024 02:12:02 +0100 Subject: [PATCH 23/27] Supply missing `Project.cache` annotation --- jaraco/develop/git.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 81e3ab2..e56ca7b 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -1,3 +1,4 @@ +from __future__ import annotations import contextlib import functools import os @@ -114,7 +115,7 @@ class Project(str): """ pattern = re.compile(r'(?P\S+)\s*(?P.*)$') - cache = {} + cache: dict[str, Project] = {} def __new__(cls, value, **kwargs): # Down-cast to a string early. From 900a0eb8ecb290c9301f34345749f8d9d605cdf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Sun, 4 Feb 2024 02:31:00 +0100 Subject: [PATCH 24/27] Use `/` instead of `posixpath.sep` in `Project.from_path` --- jaraco/develop/git.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index e56ca7b..1328bec 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -141,10 +141,10 @@ def parse(cls, line): @classmethod def from_path(self, path): from . import github - local = f'{github.username()}{posixpath.sep}' + local = f'{github.username()}/' if path.startswith(local): return self(path.removeprefix(local)) - return self(posixpath.sep + path.removeprefix(posixpath.sep)) + return self(f'/{path.removeprefix("/")}') @property def rtd_slug(self): From 835b84874073251407933a8458b800bc47a08ece Mon Sep 17 00:00:00 2001 From: bswck Date: Sun, 4 Feb 2024 17:26:41 +0100 Subject: [PATCH 25/27] Use base names of projects for determining RTD slugs --- jaraco/develop/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 1328bec..8bfe002 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -148,7 +148,7 @@ def from_path(self, path): @property def rtd_slug(self): - return self.replace('.', '').replace('_', '-') + return posixpath.basename(self).replace('.', '').replace('_', '-') @property def rtd_url(self): From c0cd9c89818e3765152f82dca0dab5a7e4350174 Mon Sep 17 00:00:00 2001 From: bswck Date: Sun, 4 Feb 2024 17:27:11 +0100 Subject: [PATCH 26/27] Add tests to new `Project` utilities --- jaraco/develop/git.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 8bfe002..0116be8 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -140,6 +140,12 @@ def parse(cls, line): @classmethod def from_path(self, path): + """ + >>> Project.from_path('pypa/setuptools') + '/pypa/setuptools' + >>> Project.from_path('jaraco/jaraco.functools') + 'jaraco.functools' + """ from . import github local = f'{github.username()}/' if path.startswith(local): @@ -148,10 +154,22 @@ def from_path(self, path): @property def rtd_slug(self): + """ + >>> Project('jaraco.functools').rtd_slug + 'jaracofunctools' + >>> Project('/pypa/setuptools_scm').rtd_slug + 'setuptools-scm' + """ return posixpath.basename(self).replace('.', '').replace('_', '-') @property def rtd_url(self): + """ + >>> Project('jaraco.functools').rtd_url + 'https://jaracofunctools.readthedocs.io/' + >>> Project('/pypa/setuptools_scm').rtd_url + 'https://setuptools-scm.readthedocs.io/' + """ return f'https://{self.rtd_slug}.readthedocs.io/' From ca808122fe82e2327fa4e84ffad08151b93b0463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Tue, 26 Mar 2024 20:52:10 +0100 Subject: [PATCH 27/27] Use `@functools.lru_cache` on `Project` factory constructor Co-authored-by: Jason R. Coombs --- jaraco/develop/git.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/jaraco/develop/git.py b/jaraco/develop/git.py index 1c801e6..ec7acd6 100644 --- a/jaraco/develop/git.py +++ b/jaraco/develop/git.py @@ -120,17 +120,10 @@ class Project(str): pattern = re.compile(r'(?P\S+)\s*(?P.*)$') tags: list[str] = [] - cache: dict[str, Project] = {} + @functools.lru_cache def __new__(cls, value, **kwargs): - # Down-cast to a string early. - value = sys.intern(str(value)) - try: - return cls.cache[value] - except KeyError: - new = super().__new__(cls, value) - cls.cache[new] = new - return new + return super().__new__(cls, value) def __init__(self, value, **kwargs): vars(self).update({'topics': [], **kwargs})