Skip to content

Commit 22724eb

Browse files
Merge pull request #666 from RonnyPfannschmidt/types-and-fixes
Types and fixes
2 parents 088c652 + eaf6d64 commit 22724eb

17 files changed

+263
-127
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ repos:
3636
additional_dependencies:
3737
- types-setuptools
3838
- tokenize-rt==3.2.0
39+
- pytest == 6.2.5

mypy.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ mypy_path = $MYPY_CONFIG_FILE_DIR/src
77
[mypy-setuptools_scm.*]
88
# disabled as it will take a bit
99
# disallow_untyped_defs = True
10+
# strict = true

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ setuptools_scm.parse_scm_fallback =
6262
PKG-INFO = setuptools_scm.hacks:parse_pkginfo
6363
pip-egg-info = setuptools_scm.hacks:parse_pip_egg_info
6464
setup.py = setuptools_scm.hacks:fallback_version
65+
pyproject.toml = setuptools_scm.hacks:fallback_version
6566
setuptools_scm.version_scheme =
6667
guess-next-dev = setuptools_scm.version:guess_next_dev_version
6768
post-release = setuptools_scm.version:postrelease_version

src/setuptools_scm/__init__.py

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
"""
55
import os
66
import warnings
7+
from typing import NoReturn
8+
from typing import Optional
79

10+
from . import _types
811
from ._entrypoints import _call_entrypoint_fn
912
from ._entrypoints import _version_from_entrypoints
1013
from ._overrides import _read_pretended_version_for
@@ -22,6 +25,7 @@
2225
from .utils import trace
2326
from .version import format_version
2427
from .version import meta
28+
from .version import ScmVersion
2529

2630
TEMPLATES = {
2731
".py": """\
@@ -42,14 +46,16 @@ def version_from_scm(root):
4246
stacklevel=2,
4347
)
4448
config = Configuration(root=root)
45-
# TODO: Is it API?
4649
return _version_from_entrypoints(config)
4750

4851

49-
def dump_version(root, version: str, write_to, template: "str | None" = None):
52+
def dump_version(
53+
root: _types.PathT,
54+
version: str,
55+
write_to: _types.PathT,
56+
template: "str | None" = None,
57+
):
5058
assert isinstance(version, str)
51-
if not write_to:
52-
return
5359
target = os.path.normpath(os.path.join(root, write_to))
5460
ext = os.path.splitext(target)[1]
5561
template = template or TEMPLATES.get(ext)
@@ -66,7 +72,7 @@ def dump_version(root, version: str, write_to, template: "str | None" = None):
6672
fp.write(template.format(version=version, version_tuple=version_tuple))
6773

6874

69-
def _do_parse(config):
75+
def _do_parse(config: Configuration) -> "ScmVersion|None":
7076
pretended = _read_pretended_version_for(config)
7177
if pretended is not None:
7278
return pretended
@@ -77,74 +83,68 @@ def _do_parse(config):
7783
raise TypeError(
7884
"version parse result was a string\nplease return a parsed version"
7985
)
80-
version = parse_result or _version_from_entrypoints(config, fallback=True)
86+
version: Optional[ScmVersion]
87+
if parse_result:
88+
assert isinstance(parse_result, ScmVersion)
89+
version = parse_result
90+
else:
91+
version = _version_from_entrypoints(config, fallback=True)
8192
else:
8293
# include fallbacks after dropping them from the main entrypoint
8394
version = _version_from_entrypoints(config) or _version_from_entrypoints(
8495
config, fallback=True
8596
)
8697

87-
if version:
88-
return version
98+
return version
99+
89100

101+
def _version_missing(config) -> NoReturn:
90102
raise LookupError(
91-
"setuptools-scm was unable to detect version for %r.\n\n"
103+
f"setuptools-scm was unable to detect version for {config.absolute_root}.\n\n"
92104
"Make sure you're either building from a fully intact git repository "
93105
"or PyPI tarballs. Most other sources (such as GitHub's tarballs, a "
94106
"git checkout without the .git folder) don't contain the necessary "
95107
"metadata and will not work.\n\n"
96108
"For example, if you're using pip, instead of "
97109
"https://github.com/user/proj/archive/master.zip "
98-
"use git+https://github.com/user/proj.git#egg=proj" % config.absolute_root
110+
"use git+https://github.com/user/proj.git#egg=proj"
99111
)
100112

101113

102-
def get_version(
103-
root=".",
104-
version_scheme=DEFAULT_VERSION_SCHEME,
105-
local_scheme=DEFAULT_LOCAL_SCHEME,
106-
write_to=None,
107-
write_to_template=None,
108-
relative_to=None,
109-
tag_regex=DEFAULT_TAG_REGEX,
110-
parentdir_prefix_version=None,
111-
fallback_version=None,
112-
fallback_root=".",
113-
parse=None,
114-
git_describe_command=None,
115-
dist_name=None,
116-
version_cls=None,
117-
normalize=True,
118-
search_parent_directories=False,
119-
):
114+
@_types.transfer_input_args(Configuration)
115+
def get_version(**kw) -> str:
120116
"""
121117
If supplied, relative_to should be a file from which root may
122118
be resolved. Typically called by a script or module that is not
123119
in the root of the repository to direct setuptools_scm to the
124120
root of the repository by supplying ``__file__``.
125121
"""
126122

127-
config = Configuration(**locals())
128-
return _get_version(config)
123+
config = Configuration(**kw)
124+
maybe_version = _get_version(config)
125+
if maybe_version is None:
126+
_version_missing(config)
127+
return maybe_version
129128

130129

131-
def _get_version(config):
130+
def _get_version(config: Configuration) -> "str|None":
132131
parsed_version = _do_parse(config)
133-
134-
if parsed_version:
135-
version_string = format_version(
136-
parsed_version,
137-
version_scheme=config.version_scheme,
138-
local_scheme=config.local_scheme,
139-
)
132+
if parsed_version is None:
133+
return None
134+
version_string = format_version(
135+
parsed_version,
136+
version_scheme=config.version_scheme,
137+
local_scheme=config.local_scheme,
138+
)
139+
if config.write_to is not None:
140140
dump_version(
141141
root=config.root,
142142
version=version_string,
143143
write_to=config.write_to,
144144
template=config.write_to_template,
145145
)
146146

147-
return version_string
147+
return version_string
148148

149149

150150
# Public API

src/setuptools_scm/__main__.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def main() -> None:
2727
config = Configuration(root=root)
2828

2929
version = _get_version(config)
30+
assert version is not None
3031
if opts.strip_dev:
3132
version = version.partition(".dev")[0]
3233
print(version)
@@ -36,7 +37,7 @@ def main() -> None:
3637
print(fname)
3738

3839

39-
def _get_cli_opts():
40+
def _get_cli_opts() -> argparse.Namespace:
4041
prog = "python -m setuptools_scm"
4142
desc = "Print project version according to SCM metadata"
4243
parser = argparse.ArgumentParser(prog, description=desc)
@@ -67,13 +68,15 @@ def _get_cli_opts():
6768
return parser.parse_args()
6869

6970

70-
def _find_pyproject(parent):
71+
def _find_pyproject(parent: str) -> str:
7172
for directory in walk_potential_roots(os.path.abspath(parent)):
7273
pyproject = os.path.join(directory, "pyproject.toml")
73-
if os.path.exists(pyproject):
74+
if os.path.isfile(pyproject):
7475
return pyproject
7576

76-
raise FileNotFoundError("'pyproject.toml' was not found")
77+
return os.path.abspath(
78+
"pyproject.toml"
79+
) # use default name to trigger the default errors
7780

7881

7982
if __name__ == "__main__":

src/setuptools_scm/_entrypoints.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .discover import iter_matching_entrypoints
66
from .utils import function_has_arg
77
from .utils import trace
8+
from setuptools_scm.version import ScmVersion
89

910

1011
def _call_entrypoint_fn(root, config, fn):
@@ -21,19 +22,23 @@ def _call_entrypoint_fn(root, config, fn):
2122
return fn(root)
2223

2324

24-
def _version_from_entrypoints(config: Configuration, fallback=False):
25+
def _version_from_entrypoints(
26+
config: Configuration, fallback: bool = False
27+
) -> "ScmVersion|None":
2528
if fallback:
2629
entrypoint = "setuptools_scm.parse_scm_fallback"
2730
root = config.fallback_root
2831
else:
2932
entrypoint = "setuptools_scm.parse_scm"
3033
root = config.absolute_root
3134

35+
trace("version_from_ep", entrypoint, root)
3236
for ep in iter_matching_entrypoints(root, entrypoint, config):
33-
version = _call_entrypoint_fn(root, config, ep.load())
37+
version: Optional[ScmVersion] = _call_entrypoint_fn(root, config, ep.load())
3438
trace(ep, version)
3539
if version:
3640
return version
41+
return None
3742

3843

3944
try:

src/setuptools_scm/_types.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import os
2+
from typing import Callable
3+
from typing import TYPE_CHECKING
4+
from typing import TypeVar
5+
from typing import Union
6+
7+
if TYPE_CHECKING:
8+
9+
from typing_extensions import ParamSpec
10+
else:
11+
12+
class ParamSpec(list):
13+
def __init__(self, _) -> None:
14+
pass
15+
16+
17+
PathT = Union["os.PathLike[str]", str]
18+
19+
20+
T = TypeVar("T")
21+
T2 = TypeVar("T2")
22+
PARAMS = ParamSpec("PARAMS")
23+
24+
25+
def transfer_input_args(
26+
template: "Callable[PARAMS, T]",
27+
) -> Callable[[Callable[..., T2]], "Callable[PARAMS, T2]"]:
28+
def decorate(func: Callable[..., T2]) -> "Callable[PARAMS, T2]":
29+
return func
30+
31+
return decorate

src/setuptools_scm/_version_cls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
Version, "release"
99
), "broken installation ensure packaging>=20 is available"
1010
except ImportError:
11-
from pkg_resources._vendor.packaging.version import (
11+
from pkg_resources._vendor.packaging.version import ( # type: ignore
1212
Version as SetuptoolsVersion,
1313
InvalidVersion,
1414
)
1515

1616
try:
1717
SetuptoolsVersion.release
18-
Version = SetuptoolsVersion
18+
Version = SetuptoolsVersion # type: ignore
1919
except AttributeError:
2020

2121
class Version(SetuptoolsVersion): # type: ignore

src/setuptools_scm/config.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
import os
33
import re
44
import warnings
5+
from typing import Type
6+
from typing import TypeVar
57

8+
from . import _types as _t
69
from ._version_cls import NonNormalizedVersion
710
from ._version_cls import Version
811
from .utils import trace
@@ -27,10 +30,13 @@ def _check_tag_regex(value):
2730
return regex
2831

2932

30-
def _check_absolute_root(root, relative_to):
31-
trace("l", repr(locals()))
33+
def _check_absolute_root(root: _t.PathT, relative_to: _t.PathT):
34+
trace("abs root", repr(locals()))
3235
if relative_to:
33-
if os.path.isabs(root) and not root.startswith(relative_to):
36+
if (
37+
os.path.isabs(root)
38+
and not os.path.commonpath([root, relative_to]) == relative_to
39+
):
3440
warnings.warn(
3541
"absolute root path '%s' overrides relative_to '%s'"
3642
% (root, relative_to)
@@ -49,33 +55,40 @@ def _check_absolute_root(root, relative_to):
4955
return os.path.abspath(root)
5056

5157

52-
def _lazy_tomli_load(data):
58+
def _lazy_tomli_load(data: str):
5359
from tomli import loads
5460

5561
return loads(data)
5662

5763

64+
VersionT = TypeVar("VersionT", Version, NonNormalizedVersion)
65+
66+
5867
class Configuration:
5968
"""Global configuration model"""
6069

70+
_root: _t.PathT
71+
_relative_to: "_t.PathT | None"
72+
version_cls: "Type[Version]|Type[NonNormalizedVersion]"
73+
6174
def __init__(
6275
self,
63-
relative_to=None,
64-
root=".",
65-
version_scheme=DEFAULT_VERSION_SCHEME,
76+
relative_to: "_t.PathT | None" = None,
77+
root: _t.PathT = ".",
78+
version_scheme: str = DEFAULT_VERSION_SCHEME,
6679
local_scheme=DEFAULT_LOCAL_SCHEME,
67-
write_to=None,
68-
write_to_template=None,
80+
write_to: "_t.PathT | None" = None,
81+
write_to_template: "str|None" = None,
6982
tag_regex=DEFAULT_TAG_REGEX,
7083
parentdir_prefix_version=None,
71-
fallback_version=None,
72-
fallback_root=".",
84+
fallback_version: "str|None" = None,
85+
fallback_root: _t.PathT = ".",
7386
parse=None,
7487
git_describe_command=None,
75-
dist_name=None,
76-
version_cls=None,
77-
normalize=True,
78-
search_parent_directories=False,
88+
dist_name: str = None,
89+
version_cls: "Type[Version]|Type[NonNormalizedVersion]|str|None" = None,
90+
normalize: bool = True,
91+
search_parent_directories: bool = False,
7992
):
8093
# TODO:
8194
self._relative_to = relative_to
@@ -107,18 +120,19 @@ def __init__(
107120
else:
108121
# Use `version_cls` if provided, default to packaging or pkg_resources
109122
if version_cls is None:
110-
version_cls = Version
123+
self.version_cls = Version
111124
elif isinstance(version_cls, str):
112125
try:
113126
# Not sure this will work in old python
114127
import importlib
115128

116129
pkg, cls_name = version_cls.rsplit(".", 1)
117130
version_cls_host = importlib.import_module(pkg)
118-
version_cls = getattr(version_cls_host, cls_name)
131+
self.version_cls = getattr(version_cls_host, cls_name)
119132
except: # noqa
120133
raise ValueError(f"Unable to import version_cls='{version_cls}'")
121-
self.version_cls = version_cls
134+
else:
135+
self.version_cls = version_cls
122136

123137
@property
124138
def fallback_root(self):

src/setuptools_scm/hacks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ def fallback_version(root, config=None):
3737
if version is not None:
3838
return meta(str(version), preformatted=True, config=config)
3939
if config.fallback_version is not None:
40+
trace("FALLBACK")
4041
return meta(config.fallback_version, preformatted=True, config=config)

0 commit comments

Comments
 (0)