common-python-tasks 0.12.1__py3-none-any.whl → 0.13.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,163 @@
1
+ import contextvars
2
+ import inspect
3
+ import logging
4
+ import os
5
+ import shlex
6
+ from collections.abc import Callable
7
+ from functools import partial, wraps
8
+ from pathlib import Path
9
+ from typing import ClassVar
10
+
11
+ from poethepoet_tasks import TaskCollection
12
+
13
+
14
+ class _ColoredFormatter(logging.Formatter):
15
+ COLORS: ClassVar = {
16
+ "WARNING": "\033[93m",
17
+ "ERROR": "\033[91m",
18
+ "CRITICAL": "\033[91m",
19
+ "RESET": "\033[0m",
20
+ }
21
+
22
+ def format(self, record: logging.LogRecord) -> str:
23
+ log_color = self.COLORS.get(record.levelname, "")
24
+ record.levelname = f"\033[1m{log_color}{record.levelname}{self.COLORS['RESET'] if log_color else ''}\033[0m"
25
+ return super().format(record)
26
+
27
+
28
+ LOGGER = logging.getLogger("common_python_tasks")
29
+ handler = logging.StreamHandler()
30
+ handler.setFormatter(_ColoredFormatter("[%(asctime)s] %(levelname)s: %(message)s"))
31
+ LOGGER.addHandler(handler)
32
+ LOGGER.setLevel(
33
+ {
34
+ "DEBUG": logging.DEBUG,
35
+ "INFO": logging.INFO,
36
+ "WARNING": logging.WARNING,
37
+ "ERROR": logging.ERROR,
38
+ "CRITICAL": logging.CRITICAL,
39
+ }[os.getenv("COMMON_PYTHON_TASKS_LOG_LEVEL", "INFO").upper()]
40
+ )
41
+
42
+
43
+ tasks = TaskCollection(
44
+ envfile=[
45
+ f
46
+ for f in [
47
+ ".env.defaults",
48
+ "project.properties",
49
+ ".env",
50
+ ]
51
+ if Path(f).exists()
52
+ ]
53
+ )
54
+
55
+ _task_call_depth: contextvars.ContextVar[int] = contextvars.ContextVar(
56
+ "common_python_tasks_task_call_depth", default=0
57
+ )
58
+
59
+ _original_script = tasks.script
60
+
61
+
62
+ def _make_var_positional_task_args_optional(
63
+ func: callable, resolved_task_name: str
64
+ ) -> None:
65
+ var_positional_arg_names = {
66
+ param.name
67
+ for param in inspect.signature(func).parameters.values()
68
+ if param.kind == inspect.Parameter.VAR_POSITIONAL
69
+ }
70
+ if not var_positional_arg_names:
71
+ return
72
+
73
+ for task_config in tasks._tasks.get(resolved_task_name, []):
74
+ for arg_config in task_config.options.get("args", []):
75
+ if arg_config.get("name") in var_positional_arg_names:
76
+ arg_config["required"] = False
77
+
78
+
79
+ def _wrap_task_function(func: callable, task_name: str | None) -> callable:
80
+ resolved_task_name = task_name or func.__name__.replace("_", "-").lower()
81
+ var_positional_arg_names = [
82
+ param.name
83
+ for param in inspect.signature(func).parameters.values()
84
+ if param.kind == inspect.Parameter.VAR_POSITIONAL
85
+ ]
86
+
87
+ @wraps(func)
88
+ def wrapped(*args, **kwargs):
89
+ for arg_name in var_positional_arg_names:
90
+ if arg_name not in kwargs:
91
+ continue
92
+ values = kwargs.pop(arg_name)
93
+ if values is None:
94
+ continue
95
+ if isinstance(values, list | tuple):
96
+ args = (*args, *values)
97
+ else:
98
+ args = (*args, values)
99
+
100
+ if var_positional_arg_names and (
101
+ poe_extra_args := os.environ.pop("POE_EXTRA_ARGS", None)
102
+ ):
103
+ args = (*args, *shlex.split(poe_extra_args))
104
+
105
+ current_depth = _task_call_depth.get()
106
+ if current_depth > 0:
107
+ LOGGER.log(
108
+ (
109
+ logging.INFO
110
+ if not resolved_task_name.startswith("_")
111
+ else logging.DEBUG
112
+ ),
113
+ "Running task %s",
114
+ resolved_task_name,
115
+ )
116
+ token = _task_call_depth.set(current_depth + 1)
117
+ try:
118
+ return func(*args, **kwargs)
119
+ finally:
120
+ _task_call_depth.reset(token)
121
+
122
+ return wrapped
123
+
124
+
125
+ def _script(
126
+ func: Callable | None = None,
127
+ task_name: str | None = None,
128
+ help: str | None = None,
129
+ task_args: bool = True,
130
+ options: dict | None = None,
131
+ tags: tuple[str, ...] | None = None,
132
+ ):
133
+
134
+ if tags is None:
135
+ tags = ()
136
+
137
+ if func is None:
138
+ return partial(
139
+ _script,
140
+ task_name=task_name,
141
+ help=help,
142
+ task_args=task_args,
143
+ options=options,
144
+ tags=tags,
145
+ )
146
+
147
+ resolved_task_name = task_name or func.__name__.replace("_", "-").lower()
148
+ wrapped = _wrap_task_function(func, resolved_task_name)
149
+ decorated = _original_script(
150
+ wrapped,
151
+ task_name=resolved_task_name,
152
+ help=help,
153
+ task_args=task_args,
154
+ options=options,
155
+ tags=tags,
156
+ )
157
+ _make_var_positional_task_args_optional(wrapped, resolved_task_name)
158
+ return decorated
159
+
160
+
161
+ tasks.script = _script
162
+
163
+ NO_PROMPT_ENV_VAR = "COMMON_PYTHON_TASKS_NO_PROMPT"