Skip to content

Instantly share code, notes, and snippets.

@a-recknagel
Created May 12, 2023 14:57
Show Gist options
  • Save a-recknagel/d0c44f81fef220d5543bfb94c52e6c0a to your computer and use it in GitHub Desktop.
Save a-recknagel/d0c44f81fef220d5543bfb94c52e6c0a to your computer and use it in GitHub Desktop.
w00t, switch/case does something useful
from collections import defaultdict
import importlib.metadata
from typing import Any
from pprint import pp
from packaging.requirements import Requirement
from packaging.markers import Op, Value, Variable
def group_deps_by_extra(pkg: str) -> dict[str, list[str]]:
"""Query metadata of an installed package for extra-install-dependencies."""
dists = importlib.metadata.metadata(pkg).get_all("Requires-Dist", []) # list of pep 508 strings
ret = defaultdict(list)
for dist in map(Requirement, dists):
if dist.marker is None:
continue
for marker in dist.marker._markers:
if (extra := get_extra(marker)) is not None:
ret[extra].append(dist.name)
return {**ret}
def get_extra_3_9(marker: Any) -> str | None:
# ugh...
if isinstance(marker, tuple) and len(marker) == 3:
var, op, val = marker
if (
isinstance(var, Variable)
and isinstance(op, Op)
and isinstance(val, Value)
):
if var.value == "extra" and op.value == "==":
return val.value
# shorter alternatives are more bug-prone and may raise ~5 types of exceptions
def get_extra_3_10(marker: Any) -> str | None:
# a lot more readable
match marker:
case (Variable(value="extra"), Op(value="=="), Value(value=extra)):
return extra
get_extra = get_extra_3_9 # result is the same with `get_extra_3_10`
if __name__ == '__main__':
pp(group_deps_by_extra("fastapi")) # has a coupe of interesting extras
# output with fastapi-0.95.1
{'all': ['email-validator',
'httpx',
'itsdangerous',
'jinja2',
'orjson',
'python-multipart',
'pyyaml',
'ujson',
'uvicorn'],
'dev': ['pre-commit', 'ruff', 'uvicorn'],
'doc': ['mdx-include',
'mkdocs-markdownextradata-plugin',
'mkdocs-material',
'mkdocs',
'pyyaml',
'typer-cli',
'typer'],
'test': ['anyio',
'black',
'coverage',
'databases',
'email-validator',
'flask',
'httpx',
'isort',
'mypy',
'orjson',
'passlib',
'peewee',
'pytest',
'python-jose',
'python-multipart',
'pyyaml',
'ruff',
'sqlalchemy',
'types-orjson',
'types-ujson',
'ujson']}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment