Skip to content

Commit 2bd7076

Browse files
authored
Add --ignore CLI option and ignore_files config field (#60)
* Add --ignore CLI option and ignore_files config option * Add configuration reference * Fix logic in `_load_configuration` * Fix outstanding warnings and errors * Vendor glob.translate from Python 3.13.4 because it isn't available in earlier versions. * Fix typo * Type vendored glob function * Vendor fnmatch._translate too * Skip glob_patterns_to_regex doctests Windows uses different path separators of course, so there is no easy way to get this doctest working on both platforms. Instead let's just skip this... * Use Debian's Machine-readable format for license file * Include vendored license in license field See for reference: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/#:~:text=Conjunctive%20%22AND%22%20operator * Refactor name to field Should be a bit more expressive.
1 parent b7a6437 commit 2bd7076

14 files changed

Lines changed: 667 additions & 182 deletions

LICENSE

Lines changed: 0 additions & 30 deletions
This file was deleted.

LICENSE.txt

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2+
Source: https://github.com/scientific-python/docstub
3+
4+
Files: *
5+
Copyright: 2025, Scientific Python Developers
6+
2024, Lars Grüter
7+
License: BSD 3-Clause
8+
9+
Files: src/docstub/_vendored/stdlib.py
10+
Copyright: 2001-2025, Python Software Foundation
11+
License: PSF-2.0
12+
13+
License: BSD-3-Clause
14+
All rights reserved.
15+
.
16+
Redistribution and use in source and binary forms, with or without
17+
modification, are permitted provided that the following conditions are met:
18+
.
19+
* Redistributions of source code must retain the above copyright notice, this
20+
list of conditions and the following disclaimer.
21+
.
22+
* Redistributions in binary form must reproduce the above copyright notice,
23+
this list of conditions and the following disclaimer in the documentation
24+
and/or other materials provided with the distribution.
25+
.
26+
* Neither the name of the vector package developers nor the names of its
27+
contributors may be used to endorse or promote products derived from
28+
this software without specific prior written permission.
29+
.
30+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
31+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
33+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
34+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
36+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
37+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
38+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40+
41+
42+
License: PSF-2.0
43+
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
44+
--------------------------------------------
45+
.
46+
1. This LICENSE AGREEMENT is between the Python Software Foundation
47+
("PSF"), and the Individual or Organization ("Licensee") accessing and
48+
otherwise using this software ("Python") in source or binary form and
49+
its associated documentation.
50+
.
51+
2. Subject to the terms and conditions of this License Agreement, PSF hereby
52+
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
53+
analyze, test, perform and/or display publicly, prepare derivative works,
54+
distribute, and otherwise use Python alone or in any derivative version,
55+
provided, however, that PSF's License Agreement and PSF's notice of copyright,
56+
i.e., "Copyright (c) 2001 Python Software Foundation; All Rights Reserved"
57+
are retained in Python alone or in any derivative version prepared by Licensee.
58+
.
59+
3. In the event Licensee prepares a derivative work that is based on
60+
or incorporates Python or any part thereof, and wants to make
61+
the derivative work available to others as provided herein, then
62+
Licensee hereby agrees to include in any such work a brief summary of
63+
the changes made to Python.
64+
.
65+
4. PSF is making Python available to Licensee on an "AS IS"
66+
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
67+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
68+
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
69+
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
70+
INFRINGE ANY THIRD PARTY RIGHTS.
71+
.
72+
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
73+
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
74+
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
75+
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
76+
.
77+
6. This License Agreement will automatically terminate upon a material
78+
breach of its terms and conditions.
79+
.
80+
7. Nothing in this License Agreement shall be deemed to create any
81+
relationship of agency, partnership, or joint venture between PSF and
82+
Licensee. This License Agreement does not grant permission to use PSF
83+
trademarks or trade name in a trademark sense to endorse or promote
84+
products or services of Licensee, or any third party.
85+
.
86+
8. By copying, installing or otherwise using Python, Licensee
87+
agrees to be bound by the terms and conditions of this License
88+
Agreement.

docs/command_line.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Options:
4343
--config PATH Set one or more configuration file(s) explicitly.
4444
Otherwise, it will look for a `pyproject.toml` or
4545
`docstub.toml` in the current directory.
46+
--ignore GLOB Ignore files matching this glob-style pattern. Can be
47+
used multiple times.
4648
--group-errors Group identical errors together and list where they
4749
occurred. Will delay showing errors until all files have
4850
been processed. Otherwise, simply report errors as the

docs/configuration.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Configuration reference
2+
3+
Docstub will automatically look for configuration in files named
4+
5+
- `pyproject.toml`, and
6+
- `docstub.toml`
7+
8+
in the current working directory.
9+
If config files are explicitly passed to the command line interface via the `--config` option, docstub won't look implicitly look for files in the current directory.
10+
Multiple configuration files can be used, whose content will be merged.
11+
12+
Out of the box, docstub makes use of an internal configuration file [`numpy_config.toml`](../src/docstub/numpy_config.toml) which provides defaults to use NumPy types.
13+
14+
15+
## Configuration fields in `[tool.docstub]`
16+
17+
All configuration must be declared inside a `[tool.docstub]` table.
18+
19+
20+
### `ignore_files`
21+
22+
- [TOML type](https://toml.io/en/latest): array of string(s)
23+
24+
Ignore files and directories matching these [glob-style patterns](https://docs.python.org/3/library/glob.html#glob.translate).
25+
Patterns that don't start with "/" are interpreted as relative to the
26+
directory that contains the Python package for which stubs are generated.
27+
28+
Example:
29+
30+
```toml
31+
[tool.docstub]
32+
ignore_files = [
33+
"**/tests",
34+
]
35+
```
36+
37+
- Will ignore any directory anywhere that is named `tests`.
38+
39+
40+
### `types`
41+
42+
- [TOML type](https://toml.io/en/latest): table, mapping string to string
43+
44+
Types and their external modules to use in docstrings.
45+
Docstub can't yet automatically discover where to import types from other packages from.
46+
Instead, you can provide this information explicitly.
47+
Any type on the left side will be associated with the given "module" on the right side.
48+
49+
Example:
50+
51+
```toml
52+
[tool.docstub.types]
53+
Path = "pathlib"
54+
NDArray = "numpy.typing"
55+
```
56+
57+
- Will allow using `Path` in docstrings and will use `from pathlib import Path` to import the type.
58+
- Will allow using `NDarray` in docstrings and will use `from numpy.typing import NDArray` to import the type.
59+
60+
61+
### `type_prefixes`
62+
63+
- [TOML type](https://toml.io/en/latest): table, mapping string to string
64+
65+
Prefixes for external modules to match types in docstrings.
66+
Docstub can't yet automatically discover where to import types from other packages from.
67+
Instead, you can provide this information explicitly.
68+
Any type in a docstring whose prefix matches the name given on the left side, will be associated with the given "module" on the right side.
69+
70+
Example:
71+
72+
```toml
73+
[tool.docstub.type_prefixes]
74+
np = "numpy"
75+
plt = "matplotlib.pyplot
76+
```
77+
78+
- Will match `np.uint8` and `np.typing.NDarray` and use `import numpy as np`.
79+
- Will match `plt.Figure` use `import matplotlib.pyplot as plt`.
80+
81+
82+
### `type_nicknames`
83+
84+
- [TOML type](https://toml.io/en/latest): table, mapping string to string
85+
86+
Nicknames for types that can be used in docstrings to describe valid Python types or annotations.
87+
88+
Example:
89+
90+
```toml
91+
[tool.docstub.type_nicknames]
92+
func = "Callable"
93+
```
94+
95+
- Will map `func` to the `Callable` type from the `typing` module.

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ maintainers = [
99
]
1010
description = "Generate Python stub files from docstrings"
1111
readme = "README.md"
12-
license = "BSD-3-Clause"
13-
license-files = ["LICENSE"]
12+
13+
# Need to include license of vendored code as well
14+
license = "BSD-3-Clause AND PSF-2.0"
15+
license-files = ["LICENSE.txt"]
16+
1417
requires-python = ">=3.12"
1518
keywords = ["typing", "stub files", "docstings", "numpydoc"]
1619
classifiers = [
@@ -120,6 +123,7 @@ run.source = ["docstub"]
120123
Path = "pathlib"
121124

122125
[tool.docstub.type_prefixes]
126+
re = "re"
123127
cst = "libcst"
124128
lark = "lark"
125129
numpydoc = "numpydoc"

src/docstub/_cli.py

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@
1515
)
1616
from ._cache import FileCache
1717
from ._config import Config
18-
from ._stubs import (
18+
from ._path_utils import (
1919
STUB_HEADER_COMMENT,
20-
Py2StubTransformer,
21-
try_format_stub,
2220
walk_python_package,
2321
walk_source_and_targets,
2422
)
23+
from ._stubs import Py2StubTransformer, try_format_stub
2524
from ._utils import ErrorReporter, GroupedErrorReporter
2625
from ._version import __version__
2726

@@ -43,22 +42,24 @@ def _load_configuration(config_paths=None):
4342
numpy_config = Config.from_toml(Config.NUMPY_PATH)
4443
config = config.merge(numpy_config)
4544

46-
pyproject_toml = Path.cwd() / "pyproject.toml"
47-
if pyproject_toml.is_file():
48-
logger.info("using %s", pyproject_toml)
49-
add_config = Config.from_toml(pyproject_toml)
50-
config = config.merge(add_config)
51-
52-
docstub_toml = Path.cwd() / "docstub.toml"
53-
if docstub_toml.is_file():
54-
logger.info("using %s", docstub_toml)
55-
add_config = Config.from_toml(docstub_toml)
56-
config = config.merge(add_config)
57-
58-
for path in config_paths:
59-
logger.info("using %s", path)
60-
add_config = Config.from_toml(path)
61-
config = config.merge(add_config)
45+
if config_paths:
46+
for path in config_paths:
47+
logger.info("using %s", path)
48+
add_config = Config.from_toml(path)
49+
config = config.merge(add_config)
50+
51+
else:
52+
pyproject_toml = Path.cwd() / "pyproject.toml"
53+
if pyproject_toml.is_file():
54+
logger.info("using %s", pyproject_toml)
55+
add_config = Config.from_toml(pyproject_toml)
56+
config = config.merge(add_config)
57+
58+
docstub_toml = Path.cwd() / "docstub.toml"
59+
if docstub_toml.is_file():
60+
logger.info("using %s", docstub_toml)
61+
add_config = Config.from_toml(docstub_toml)
62+
config = config.merge(add_config)
6263

6364
return config
6465

@@ -78,12 +79,17 @@ def _setup_logging(*, verbose):
7879
)
7980

8081

81-
def _collect_types(root_path):
82+
def _collect_types(root_path, *, ignore=()):
8283
"""Collect types.
8384
8485
Parameters
8586
----------
8687
root_path : Path
88+
ignore : Sequence[str], optional
89+
Don't yield files matching these glob-like patterns. The pattern is
90+
interpreted relative to the root of the Python package unless it starts
91+
with "/". See :ref:`glob.translate(..., recursive=True, include_hidden=True)`
92+
for more details on the precise implementation.
8793
8894
Returns
8995
-------
@@ -98,7 +104,7 @@ def _collect_types(root_path):
98104
name=f"{__version__}/collected_types",
99105
)
100106
if root_path.is_dir():
101-
for source_path in walk_python_package(root_path):
107+
for source_path in walk_python_package(root_path, ignore=ignore):
102108
logger.info("collecting types in %s", source_path)
103109
types_in_source = collect_cached_types(source_path)
104110
types.update(types_in_source)
@@ -162,6 +168,13 @@ def cli():
162168
"Otherwise, it will look for a `pyproject.toml` or `docstub.toml` in the "
163169
"current directory.",
164170
)
171+
@click.option(
172+
"--ignore",
173+
type=str,
174+
multiple=True,
175+
metavar="GLOB",
176+
help="Ignore files matching this glob-style pattern. Can be used multiple times.",
177+
)
165178
@click.option(
166179
"--group-errors",
167180
is_flag=True,
@@ -182,7 +195,7 @@ def cli():
182195
@click.option("-v", "--verbose", count=True, help="Print more details (repeatable).")
183196
@click.help_option("-h", "--help")
184197
@report_execution_time()
185-
def run(root_path, out_dir, config_paths, group_errors, allow_errors, verbose):
198+
def run(root_path, out_dir, config_paths, ignore, group_errors, allow_errors, verbose):
186199
"""Generate Python stub files.
187200
188201
Given a `PACKAGE_PATH` to a Python package, generate stub files for it.
@@ -194,7 +207,8 @@ def run(root_path, out_dir, config_paths, group_errors, allow_errors, verbose):
194207
----------
195208
root_path : Path
196209
out_dir : Path
197-
config_paths : list[Path]
210+
config_paths : Sequence[Path]
211+
ignore : Sequence[str]
198212
group_errors : bool
199213
allow_errors : int
200214
verbose : str
@@ -212,9 +226,10 @@ def run(root_path, out_dir, config_paths, group_errors, allow_errors, verbose):
212226
)
213227

214228
config = _load_configuration(config_paths)
229+
config = config.merge(Config(ignore_files=list(ignore)))
215230

216231
types = common_known_types()
217-
types |= _collect_types(root_path)
232+
types |= _collect_types(root_path, ignore=config.ignore_files)
218233
types |= {
219234
type_name: KnownImport(import_path=module, import_name=type_name)
220235
for type_name, module in config.types.items()
@@ -245,7 +260,9 @@ def run(root_path, out_dir, config_paths, group_errors, allow_errors, verbose):
245260

246261
# Stub generation ---------------------------------------------------------
247262

248-
for source_path, stub_path in walk_source_and_targets(root_path, out_dir):
263+
for source_path, stub_path in walk_source_and_targets(
264+
root_path, out_dir, ignore=config.ignore_files
265+
):
249266
if source_path.suffix.lower() == ".pyi":
250267
logger.debug("using existing stub file %s", source_path)
251268
with source_path.open() as fo:

0 commit comments

Comments
 (0)