-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #374 from duckinator/remolisher/config
Use a common configuration model
- Loading branch information
Showing
11 changed files
with
186 additions
and
128 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from collections.abc import Mapping, Set, Sequence | ||
from functools import partial, reduce | ||
from pathlib import Path | ||
from typing import Annotated, Optional # after Py3.10, replace string annotations with Self | ||
|
||
from pydantic import dataclasses, BeforeValidator, TypeAdapter | ||
|
||
try: | ||
import tomllib | ||
except ImportError: | ||
# Py3.10 compatibility shim | ||
import toml as tomllib # type: ignore | ||
|
||
|
||
# TODO(nicoo): tie model definitions into CLI parsing | ||
|
||
# Ensure we don't accidentally make non-frozen or non-kw-only dataclasses | ||
dataclass = partial(dataclasses.dataclass, frozen = True, kw_only = True) | ||
|
||
@dataclass | ||
class ReleaseConfig: | ||
# Related CLI flags: dry_run, pypi_repository | ||
github: bool = False | ||
github_release_globs: Set[str] = frozenset(("./dist/*.pyz", )) | ||
github_repository: Optional[str] = None # TODO(nicoo) refine type | ||
|
||
pypi: bool = True | ||
strip_zipapp_version: bool = False | ||
|
||
@dataclass | ||
class ZipappConfig: | ||
enabled: bool = False # args.zipapp | ||
main: Optional[str] = None # args.zipapp_main | ||
# TODO(nicoo): specify entrypoint format w/ regex annotation | ||
|
||
|
||
Commands = Annotated[ | ||
Sequence[str], | ||
BeforeValidator(lambda x, _: (x, ) if isinstance(x, str) else x), | ||
] | ||
|
||
@dataclass | ||
class ToolConfig: | ||
aliases: Mapping[str, Commands] = dataclasses.Field(default_factory = dict) | ||
release: ReleaseConfig = ReleaseConfig() | ||
|
||
python_interpreter: str = "/usr/bin/env python3" # TODO: move to ZipappConfig | ||
zipapp: ZipappConfig = ZipappConfig() | ||
|
||
ToolConfigAdapter = TypeAdapter(ToolConfig) | ||
|
||
|
||
@dataclass | ||
class Config: | ||
bork: ToolConfig | ||
project_name: Optional[str] = None | ||
|
||
@classmethod | ||
def from_project(cls, root: Path) -> 'Config': | ||
try: | ||
# Inefficient but necessary for compatibility with toml shim | ||
# To be improved once Py3.10 support is removed | ||
pyproject = tomllib.loads((root / "pyproject.toml").read_text()) | ||
except FileNotFoundError: | ||
if any((root / fn).exists() for fn in ("setup.py", "setup.cfg")): | ||
# Legacy setuptools project without Bork-specific config | ||
pyproject = {} | ||
else: | ||
raise | ||
|
||
def get(*ks): | ||
return reduce(lambda d, k: d.get(k, {}), ks, pyproject) | ||
|
||
# TODO(nicoo) figure out why mypy doesn't accept this | ||
# according to the documentation it should | ||
return Config( # type: ignore | ||
bork = ToolConfigAdapter.validate_python(get("tool", "bork")), | ||
project_name = get("project", "name") or None, # get may return {} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from os import getenv | ||
from typing import Optional | ||
|
||
from pydantic.dataclasses import dataclass | ||
|
||
@dataclass(frozen = True, kw_only = True) | ||
class Credentials: | ||
github: Optional[str] = None | ||
pypi: Optional['Credentials.PyPI'] = None | ||
|
||
@classmethod | ||
def from_env(cls) -> 'Credentials': | ||
# TODO: support specifying another env than os.environ? | ||
return cls( | ||
github = getenv("BORK_GITHUB_TOKEN"), | ||
pypi = cls.PyPI.from_env(), | ||
) | ||
|
||
@dataclass(frozen = True) | ||
class PyPI: | ||
username: str | ||
password: str | ||
|
||
@classmethod | ||
def from_env(cls) -> Optional['Credentials.PyPI']: | ||
match getenv("BORK_PYPI_USERNAME"), getenv("BORK_PYPI_PASSWORD"), getenv("BORK_GITHUB_TOKEN"): | ||
case username, password, None if username and password: | ||
return cls(username, password) | ||
case None, None, token if token: | ||
return cls("__token__", token) | ||
case None, None, None: | ||
return None | ||
|
||
# Error cases | ||
case _, password, token if password and token: | ||
raise RuntimeError("Cannot specify both BORK_PYPI_{PASSWORD, TOKEN}") | ||
case (x, None, _) | (None, x, _) if x: | ||
raise RuntimeError( | ||
"Either both or none of BORK_PYPI_{USERNAME, PASSWORD} must be specified" | ||
) | ||
|
||
# mypy cannot check for completeness of pattern matching (yet?) | ||
raise AssertionError("Accidentally-incomplete pattern matching") |
Oops, something went wrong.