Skip to content

Commit fbbcaa8

Browse files
committed
fix everything.
1 parent 6e9f62a commit fbbcaa8

15 files changed

+216
-322
lines changed

.pre-commit-config.yaml

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ repos:
33
rev: v4.4.0
44
hooks:
55
- id: check-added-large-files
6-
args:
7-
- --maxkb=25
6+
args: [--maxkb=25]
87
- id: check-case-conflict
98
- id: check-merge-conflict
109
- id: check-vcs-permalinks
@@ -14,9 +13,7 @@ repos:
1413
- id: fix-byte-order-marker
1514
- id: mixed-line-ending
1615
- id: no-commit-to-branch
17-
args:
18-
- --branch
19-
- main
16+
args: [--branch, main]
2017
- id: trailing-whitespace
2118
- repo: https://github.com/pre-commit/pygrep-hooks
2219
rev: v1.10.0
@@ -31,25 +28,11 @@ repos:
3128
rev: v3.12.0
3229
hooks:
3330
- id: reorder-python-imports
34-
args:
35-
- --py38-plus
36-
- --add-import
37-
- from __future__ import annotations
31+
args: [--py38-plus, --add-import, from __future__ import annotations]
3832
- repo: https://github.com/asottile/setup-cfg-fmt
3933
rev: v2.5.0
4034
hooks:
4135
- id: setup-cfg-fmt
42-
- repo: https://github.com/PyCQA/docformatter
43-
rev: v1.7.5
44-
hooks:
45-
- id: docformatter
46-
args:
47-
- --in-place
48-
- --wrap-summaries
49-
- '88'
50-
- --wrap-descriptions
51-
- '88'
52-
- --blank
5336
- repo: https://github.com/psf/black
5437
rev: 23.9.1
5538
hooks:
@@ -62,28 +45,20 @@ repos:
6245
rev: v1.21.0
6346
hooks:
6447
- id: refurb
65-
args:
66-
- --ignore
67-
- FURB126
48+
args: [--ignore, FURB126]
6849
- repo: https://github.com/econchick/interrogate
6950
rev: 1.5.0
7051
hooks:
7152
- id: interrogate
72-
args:
73-
- -v
74-
- --fail-under=40
75-
- src
76-
- tests
53+
args: [-v, --fail-under=40, src, tests]
7754
- repo: https://github.com/executablebooks/mdformat
7855
rev: 0.7.17
7956
hooks:
8057
- id: mdformat
8158
additional_dependencies:
8259
- mdformat-gfm
8360
- mdformat-black
84-
args:
85-
- --wrap
86-
- '88'
61+
args: [--wrap, '88']
8762
- repo: https://github.com/codespell-project/codespell
8863
rev: v2.2.6
8964
hooks:
@@ -92,23 +67,17 @@ repos:
9267
rev: v1.5.1
9368
hooks:
9469
- id: mypy
95-
args:
96-
- --no-strict-optional
97-
- --ignore-missing-imports
9870
additional_dependencies:
9971
- attrs
100-
- click
72+
- pytask
10173
- types-setuptools
10274
pass_filenames: false
10375
- repo: https://github.com/mgedmin/check-manifest
10476
rev: '0.49'
10577
hooks:
10678
- id: check-manifest
107-
args:
108-
- --no-build-isolation
109-
additional_dependencies:
110-
- setuptools-scm
111-
- toml
79+
args: [--no-build-isolation]
80+
additional_dependencies: [setuptools-scm, toml]
11281
- repo: meta
11382
hooks:
11483
- id: check-hooks-apply

CHANGES.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ chronological order. Releases follow [semantic versioning](https://semver.org/)
55
releases are available on [PyPI](https://pypi.org/project/pytask-stata) and
66
[Anaconda.org](https://anaconda.org/conda-forge/pytask-stata).
77

8-
## 0.3.0 - 2023-xx-xx
8+
## 0.4.0 - 2023-10-08
9+
10+
- {pull}`31` makes pytask-stata compatible with pytask v0.4.0.
11+
12+
## 0.3.0 - 2023-01-23
913

1014
- {pull}`24` adds ruff and refurb.
1115
- {pull}`25` adds docformatter.

environment.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ dependencies:
1111
- toml
1212

1313
# Package dependencies
14-
- pytask <0.4
15-
- pytask-parallel <0.4
14+
- pytask >=0.4.0
15+
- pytask-parallel >=0.4.0
1616

1717
# Misc
1818
- black

setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ project_urls =
2525
packages = find:
2626
install_requires =
2727
click
28-
pytask>=0.3
28+
pluggy>=1.0.0
29+
pytask>=0.4.0
2930
python_requires = >=3.8
3031
include_package_data = True
3132
package_dir = =src

src/pytask_stata/collect.py

Lines changed: 120 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,41 @@
11
"""Collect tasks."""
22
from __future__ import annotations
33

4-
import functools
54
import subprocess
6-
from types import FunctionType
5+
import warnings
76
from typing import Any
8-
from typing import TYPE_CHECKING
97

10-
from pytask import depends_on
118
from pytask import has_mark
129
from pytask import hookimpl
10+
from pytask import is_task_function
1311
from pytask import Mark
14-
from pytask import parse_nodes
15-
from pytask import produces
12+
from pytask import NodeInfo
13+
from pytask import parse_dependencies_from_task_function
14+
from pytask import parse_products_from_task_function
15+
from pytask import PathNode
16+
from pytask import PTask
17+
from pytask import PythonNode
1618
from pytask import remove_marks
1719
from pytask import Session
1820
from pytask import Task
21+
from pytask import TaskWithoutPath
1922
from pytask_stata.shared import convert_task_id_to_name_of_log_file
2023
from pytask_stata.shared import stata
24+
from pathlib import Path
2125

22-
if TYPE_CHECKING:
23-
from pathlib import Path
2426

2527

2628
def run_stata_script(
27-
executable: str, script: Path, options: list[str], log_name: list[str], cwd: Path
29+
_executable: str,
30+
_script: Path,
31+
_options: list[str],
32+
_log_name: list[str],
33+
_cwd: Path,
2834
) -> None:
2935
"""Run an R script."""
30-
cmd = [executable, "-e", "do", script.as_posix(), *options, *log_name]
36+
cmd = [_executable, "-e", "do", _script.as_posix(), *_options, *_log_name]
3137
print("Executing " + " ".join(cmd) + ".") # noqa: T201
32-
subprocess.run(cmd, cwd=cwd, check=True) # noqa: S603
38+
subprocess.run(cmd, cwd=_cwd, check=True) # noqa: S603
3339

3440

3541
@hookimpl
@@ -41,11 +47,11 @@ def pytask_collect_task(
4147

4248
if (
4349
(name.startswith("task_") or has_mark(obj, "task"))
44-
and callable(obj)
50+
and is_task_function(obj)
4551
and has_mark(obj, "stata")
4652
):
53+
# Parse the @pytask.mark.stata decorator.
4754
obj, marks = remove_marks(obj, "stata")
48-
4955
if len(marks) > 1:
5056
raise ValueError(
5157
f"Task {name!r} has multiple @pytask.mark.stata marks, but only one is "
@@ -57,47 +63,120 @@ def pytask_collect_task(
5763

5864
obj.pytask_meta.markers.append(mark)
5965

60-
dependencies = parse_nodes(session, path, name, obj, depends_on)
61-
products = parse_nodes(session, path, name, obj, produces)
66+
# Collect the nodes in @pytask.mark.julia and validate them.
67+
path_nodes = Path.cwd() if path is None else path.parent
6268

63-
markers = obj.pytask_meta.markers if hasattr(obj, "pytask_meta") else []
64-
kwargs = obj.pytask_meta.kwargs if hasattr(obj, "pytask_meta") else {}
65-
66-
task = Task(
67-
base_name=name,
68-
path=path,
69-
function=_copy_func(run_stata_script), # type: ignore[arg-type]
70-
depends_on=dependencies,
71-
produces=products,
72-
markers=markers,
73-
kwargs=kwargs,
74-
)
69+
if isinstance(script, str):
70+
warnings.warn(
71+
"Passing a string to the @pytask.mark.stata parameter 'script' is "
72+
"deprecated. Please, use a pathlib.Path instead.",
73+
stacklevel=1,
74+
)
75+
script = Path(script)
7576

7677
script_node = session.hook.pytask_collect_node(
77-
session=session, path=path, node=script
78+
session=session,
79+
path=path_nodes,
80+
node_info=NodeInfo(
81+
arg_name="script", path=(), value=script, task_path=path, task_name=name
82+
),
83+
)
84+
85+
if not (isinstance(script_node, PathNode) and script_node.path.suffix == ".do"):
86+
raise ValueError(
87+
"The 'script' keyword of the @pytask.mark.stata decorator must point "
88+
f"to a file with the .do suffix, but it is {script_node}."
89+
)
90+
91+
options_node = session.hook.pytask_collect_node(
92+
session=session,
93+
path=path_nodes,
94+
node_info=NodeInfo(
95+
arg_name="_options",
96+
path=(),
97+
value=options,
98+
task_path=path,
99+
task_name=name,
100+
),
101+
)
102+
103+
executable_node = session.hook.pytask_collect_node(
104+
session=session,
105+
path=path_nodes,
106+
node_info=NodeInfo(
107+
arg_name="_executable",
108+
path=(),
109+
value=session.config["stata"],
110+
task_path=path,
111+
task_name=name,
112+
),
113+
)
114+
115+
cwd_node = session.hook.pytask_collect_node(
116+
session=session,
117+
path=path_nodes,
118+
node_info=NodeInfo(
119+
arg_name="_cwd",
120+
path=(),
121+
value=path.parent,
122+
task_path=path,
123+
task_name=name,
124+
),
125+
)
126+
127+
dependencies = parse_dependencies_from_task_function(
128+
session, path, name, path_nodes, obj
78129
)
130+
products = parse_products_from_task_function(
131+
session, path, name, path_nodes, obj
132+
)
133+
134+
# Add script
135+
dependencies["_script"] = script_node
136+
dependencies["_options"] = options_node
137+
dependencies["_cwd"] = cwd_node
138+
dependencies["_executable"] = executable_node
139+
140+
markers = obj.pytask_meta.markers if hasattr(obj, "pytask_meta") else []
79141

80-
if isinstance(task.depends_on, dict):
81-
task.depends_on["__script"] = script_node
142+
task: PTask
143+
if path is None:
144+
task = TaskWithoutPath(
145+
name=name,
146+
function=run_stata_script,
147+
depends_on=dependencies,
148+
produces=products,
149+
markers=markers,
150+
)
82151
else:
83-
task.depends_on = {0: task.depends_on, "__script": script_node}
152+
task = Task(
153+
base_name=name,
154+
path=path,
155+
function=run_stata_script,
156+
depends_on=dependencies,
157+
produces=products,
158+
markers=markers,
159+
)
84160

161+
# Add log_name node that depends on the task id.
85162
if session.config["platform"] == "win32":
86-
log_name = convert_task_id_to_name_of_log_file(task.short_name)
163+
log_name = convert_task_id_to_name_of_log_file(task)
87164
log_name_arg = [f"-{log_name}"]
88165
else:
89166
log_name_arg = []
90167

91-
stata_function = functools.partial(
92-
task.function,
93-
executable=session.config["stata"],
94-
script=task.depends_on["__script"].path,
95-
options=options,
96-
log_name=log_name_arg,
97-
cwd=task.path.parent,
168+
log_name_node = session.hook.pytask_collect_node(
169+
session=session,
170+
path=path_nodes,
171+
node_info=NodeInfo(
172+
arg_name="_log_name",
173+
path=(),
174+
value=PythonNode(value=log_name_arg),
175+
task_path=path,
176+
task_name=name,
177+
),
98178
)
99-
100-
task.function = stata_function
179+
task.depends_on["_log_name"] = log_name_node
101180

102181
return task
103182
return None
@@ -111,28 +190,3 @@ def _parse_stata_mark(mark: Mark) -> Mark:
111190

112191
mark = Mark("stata", (), parsed_kwargs)
113192
return mark
114-
115-
116-
def _copy_func(func: FunctionType) -> FunctionType:
117-
"""Create a copy of a function.
118-
119-
Based on https://stackoverflow.com/a/13503277/7523785.
120-
121-
Example
122-
-------
123-
>>> def _func(): pass
124-
>>> copied_func = _copy_func(_func)
125-
>>> _func is copied_func
126-
False
127-
128-
"""
129-
new_func = FunctionType(
130-
func.__code__,
131-
func.__globals__,
132-
name=func.__name__,
133-
argdefs=func.__defaults__,
134-
closure=func.__closure__,
135-
)
136-
new_func = functools.update_wrapper(new_func, func)
137-
new_func.__kwdefaults__ = func.__kwdefaults__
138-
return new_func

0 commit comments

Comments
 (0)