-
-
Notifications
You must be signed in to change notification settings - Fork 220
/
Copy path_config.py
153 lines (129 loc) · 4.89 KB
/
_config.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""configuration"""
from __future__ import annotations
import dataclasses
import os
import re
import warnings
from pathlib import Path
from typing import Any
from typing import Pattern
from typing import Protocol
from . import _log
from . import _types as _t
from ._integration.pyproject_reading import (
get_args_for_pyproject as _get_args_for_pyproject,
)
from ._integration.pyproject_reading import read_pyproject as _read_pyproject
from ._overrides import read_toml_overrides
from ._version_cls import Version as _Version
from ._version_cls import _validate_version_cls
from ._version_cls import _VersionT
log = _log.log.getChild("config")
DEFAULT_TAG_REGEX = re.compile(
r"^(?:[\w-]+-)?(?P<version>[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$"
)
"""default tag regex that tries to match PEP440 style versions
with prefix consisting of dashed words"""
DEFAULT_VERSION_SCHEME = "guess-next-dev"
DEFAULT_LOCAL_SCHEME = "node-and-date"
def _check_tag_regex(value: str | Pattern[str] | None) -> Pattern[str]:
if not value:
regex = DEFAULT_TAG_REGEX
else:
regex = re.compile(value)
group_names = regex.groupindex.keys()
if regex.groups == 0 or (regex.groups > 1 and "version" not in group_names):
warnings.warn(
"Expected tag_regex to contain a single match group or a group named"
" 'version' to identify the version part of any tag."
)
return regex
class ParseFunction(Protocol):
def __call__(
self, root: _t.PathT, *, config: Configuration
) -> _t.SCMVERSION | None: ...
def _check_absolute_root(root: _t.PathT, relative_to: _t.PathT | None) -> str:
log.debug("check absolute root=%s relative_to=%s", root, relative_to)
if relative_to:
if (
os.path.isabs(root)
and os.path.isabs(relative_to)
and not os.path.commonpath([root, relative_to]) == root
):
warnings.warn(
f"absolute root path '{root}' overrides relative_to '{relative_to}'"
)
if os.path.isdir(relative_to):
warnings.warn(
"relative_to is expected to be a file,"
f" its the directory {relative_to}\n"
"assuming the parent directory was passed"
)
log.debug("dir %s", relative_to)
root = os.path.join(relative_to, root)
else:
log.debug("file %s", relative_to)
root = os.path.join(os.path.dirname(relative_to), root)
return os.path.abspath(root)
@dataclasses.dataclass
class Configuration:
"""Global configuration model"""
relative_to: _t.PathT | None = None
root: _t.PathT = "."
version_scheme: _t.VERSION_SCHEME = DEFAULT_VERSION_SCHEME
local_scheme: _t.VERSION_SCHEME = DEFAULT_LOCAL_SCHEME
tag_regex: Pattern[str] = DEFAULT_TAG_REGEX
parentdir_prefix_version: str | None = None
fallback_version: str | None = None
fallback_root: _t.PathT = "."
write_to: _t.PathT | None = None
write_to_template: str | None = None
version_file: _t.PathT | None = None
version_file_template: str | None = None
parse: ParseFunction | None = None
git_describe_command: _t.CMD_TYPE | None = None
dist_name: str | None = None
version_cls: type[_VersionT] = _Version
search_parent_directories: bool = False
micro_version_factor: int = 1000
parent: _t.PathT | None = None
@property
def absolute_root(self) -> str:
return _check_absolute_root(self.root, self.relative_to)
@classmethod
def from_file(
cls,
name: str | os.PathLike[str] = "pyproject.toml",
dist_name: str | None = None,
_require_section: bool = True,
**kwargs: Any,
) -> Configuration:
"""
Read Configuration from pyproject.toml (or similar).
Raises exceptions when file is not found or toml is
not installed or the file has invalid format or does
not contain the [tool.setuptools_scm] section.
"""
pyproject_data = _read_pyproject(Path(name), require_section=_require_section)
args = _get_args_for_pyproject(pyproject_data, dist_name, kwargs)
args.update(read_toml_overrides(args["dist_name"]))
relative_to = args.pop("relative_to", name)
return cls.from_data(relative_to=relative_to, data=args)
@classmethod
def from_data(
cls, relative_to: str | os.PathLike[str], data: dict[str, Any]
) -> Configuration:
"""
given configuration data
create a config instance after validating tag regex/version class
"""
tag_regex = _check_tag_regex(data.pop("tag_regex", None))
version_cls = _validate_version_cls(
data.pop("version_cls", None), data.pop("normalize", True)
)
return cls(
relative_to=relative_to,
version_cls=version_cls,
tag_regex=tag_regex,
**data,
)