|
4 | 4 | from __future__ import annotations |
5 | 5 |
|
6 | 6 | import argparse |
7 | | -import hashlib |
8 | 7 | import importlib |
9 | 8 | import inspect |
10 | 9 | import logging |
|
16 | 15 | from contextlib import AbstractContextManager |
17 | 16 | from contextlib import contextmanager |
18 | 17 | from contextlib import nullcontext |
19 | | -from functools import cached_property |
20 | 18 | from functools import partial |
21 | 19 | from subprocess import CompletedProcess |
22 | 20 | from types import FunctionType |
|
28 | 26 | from typing import TypeVar |
29 | 27 | from typing import cast |
30 | 28 |
|
31 | | -import attr |
32 | 29 | import requests |
33 | 30 | import rich |
34 | 31 | from rich.console import Console |
35 | 32 | from rich.theme import Theme |
36 | 33 |
|
37 | | -from ptscripts import CWD |
38 | 34 | from ptscripts import logs |
39 | 35 | from ptscripts import process |
40 | 36 | from ptscripts.virtualenv import VirtualEnv |
41 | 37 | from ptscripts.virtualenv import VirtualEnvConfig |
42 | | -from ptscripts.virtualenv import _cast_to_pathlib_path |
43 | 38 |
|
44 | 39 | if sys.version_info < (3, 10): |
45 | 40 | from typing_extensions import Concatenate |
|
76 | 71 | OriginalFunc = Callable[Param, RetType] |
77 | 72 | DecoratedFunc = Callable[Concatenate[str, Param], RetType] |
78 | 73 |
|
79 | | -if "TOOLS_SCRIPTS_PATH" in os.environ: |
80 | | - TOOLS_SCRIPTS_PATH = pathlib.Path(os.environ["TOOLS_SCRIPTS_PATH"]).resolve() |
81 | | -else: |
82 | | - TOOLS_SCRIPTS_PATH = CWD |
83 | | - |
84 | | -TOOLS_BASE_PATH = TOOLS_SCRIPTS_PATH / ".tools" |
85 | | -TOOLS_DEPS_PATH = TOOLS_BASE_PATH / "deps" |
86 | | - |
87 | | - |
88 | 74 | log = logging.getLogger(__name__) |
89 | 75 |
|
90 | 76 |
|
@@ -293,122 +279,33 @@ def web(self) -> requests.Session: |
293 | 279 | return requests.Session() |
294 | 280 |
|
295 | 281 |
|
296 | | -@attr.s(frozen=True) |
297 | | -class DefaultRequirementsConfig: |
298 | | - """ |
299 | | - Default tools requirements configuration typing. |
300 | | - """ |
301 | | - |
302 | | - requirements: list[str] = attr.ib(factory=list) |
303 | | - requirements_files: list[pathlib.Path] = attr.ib(factory=list) |
304 | | - pip_args: list[str] = attr.ib(factory=list) |
305 | | - base_tools_dir: pathlib.Path = attr.ib(init=False) |
306 | | - deps_dir: pathlib.Path = attr.ib(init=False) |
307 | | - |
308 | | - @base_tools_dir.default |
309 | | - def _default_base_tools_dir(self) -> pathlib.Path: |
310 | | - base_tools_path = TOOLS_SCRIPTS_PATH / ".tools" |
311 | | - base_tools_path.mkdir(exist_ok=True) |
312 | | - return base_tools_path |
313 | | - |
314 | | - @deps_dir.default |
315 | | - def _default_deps_dir(self) -> pathlib.Path: |
316 | | - default_deps_dir = self.base_tools_dir / "deps" |
317 | | - default_deps_dir.mkdir(exist_ok=True) |
318 | | - return default_deps_dir |
319 | | - |
320 | | - @cached_property |
321 | | - def requirements_hash(self) -> str: |
322 | | - """ |
323 | | - Returns a sha256 hash of the requirements. |
324 | | - """ |
325 | | - requirements_hash = hashlib.sha256() |
326 | | - hash_seed = os.environ.get("TOOLS_VIRTUALENV_CACHE_SEED", "") |
327 | | - requirements_hash.update(hash_seed.encode()) |
328 | | - if self.pip_args: |
329 | | - for argument in self.pip_args: |
330 | | - requirements_hash.update(argument.encode()) |
331 | | - if self.requirements: |
332 | | - for requirement in sorted(self.requirements): |
333 | | - requirements_hash.update(requirement.encode()) |
334 | | - if self.requirements_files: |
335 | | - for fpath in sorted(self.requirements_files): |
336 | | - with _cast_to_pathlib_path(fpath).open("rb") as rfh: |
337 | | - try: |
338 | | - digest = hashlib.file_digest(rfh, "sha256") # type: ignore[attr-defined] |
339 | | - except AttributeError: |
340 | | - # Python < 3.11 |
341 | | - buf = bytearray(2**18) # Reusable buffer to reduce allocations. |
342 | | - view = memoryview(buf) |
343 | | - digest = hashlib.sha256() |
344 | | - while True: |
345 | | - size = rfh.readinto(buf) |
346 | | - if size == 0: |
347 | | - break # EOF |
348 | | - digest.update(view[:size]) |
349 | | - requirements_hash.update(digest.digest()) |
350 | | - return requirements_hash.hexdigest() |
351 | | - |
352 | | - def install(self, ctx: Context) -> None: |
353 | | - """ |
354 | | - Install default requirements. |
355 | | - """ |
356 | | - requirements_hash_file = self.deps_dir / ".requirements.hash" |
357 | | - if ( |
358 | | - requirements_hash_file.exists() |
359 | | - and requirements_hash_file.read_text() == self.requirements_hash |
360 | | - ): |
361 | | - # Requirements are up to date |
362 | | - ctx.debug("Base tools requirements haven't changed.") |
363 | | - return |
364 | | - requirements = [] |
365 | | - if self.requirements_files: |
366 | | - for fpath in self.requirements_files: |
367 | | - requirements.extend(["-r", str(fpath)]) |
368 | | - if self.requirements: |
369 | | - requirements.extend(self.requirements) |
370 | | - if requirements: |
371 | | - ctx.info("Installing base tools requirements ...") |
372 | | - ctx.run( |
373 | | - sys.executable, |
374 | | - "-m", |
375 | | - "pip", |
376 | | - "install", |
377 | | - "--prefix", |
378 | | - str(self.deps_dir), |
379 | | - *self.pip_args, |
380 | | - *requirements, |
381 | | - ) |
382 | | - requirements_hash_file.write_text(self.requirements_hash) |
383 | | - |
384 | | - |
385 | | -class DefaultToolsPythonRequirements: |
| 282 | +class DefaultVirtualEnv: |
386 | 283 | """ |
387 | 284 | Simple class to hold registered imports. |
388 | 285 | """ |
389 | 286 |
|
390 | | - _instance: DefaultToolsPythonRequirements | None = None |
391 | | - reqs_config: DefaultRequirementsConfig | None |
| 287 | + _instance: DefaultVirtualEnv | None = None |
| 288 | + venv_config: VirtualEnvConfig | None |
392 | 289 |
|
393 | | - def __new__(cls) -> DefaultToolsPythonRequirements: |
| 290 | + def __new__(cls) -> DefaultVirtualEnv: |
394 | 291 | """ |
395 | 292 | Method that instantiates a singleton class and returns it. |
396 | 293 | """ |
397 | 294 | if cls._instance is None: |
398 | 295 | instance = super().__new__(cls) |
399 | | - instance.reqs_config = None |
| 296 | + instance.venv_config = None |
400 | 297 | cls._instance = instance |
401 | 298 | return cls._instance |
402 | 299 |
|
403 | 300 | @classmethod |
404 | | - def set_default_requirements_config(cls, reqs_config: DefaultRequirementsConfig) -> None: |
| 301 | + def set_default_virtualenv_config(cls, venv_config: VirtualEnvConfig) -> None: |
405 | 302 | """ |
406 | 303 | Set the default tools requirements configuration. |
407 | 304 | """ |
408 | 305 | instance = cls._instance |
409 | 306 | if instance is None: |
410 | 307 | instance = cls() |
411 | | - instance.reqs_config = reqs_config |
| 308 | + instance.venv_config = venv_config |
412 | 309 |
|
413 | 310 |
|
414 | 311 | class RegisteredImports: |
@@ -547,25 +444,33 @@ def __new__(cls) -> Parser: |
547 | 444 | return cls._instance |
548 | 445 |
|
549 | 446 | def _process_registered_tool_modules(self) -> None: |
550 | | - default_reqs_config = DefaultToolsPythonRequirements().reqs_config |
551 | | - if default_reqs_config: |
552 | | - default_reqs_config.install(self.context) |
553 | | - for module_name, venv_config in RegisteredImports(): |
554 | | - venv: VirtualEnv | AbstractContextManager[None] |
555 | | - if venv_config: |
556 | | - if "name" not in venv_config: |
557 | | - venv_config["name"] = module_name |
558 | | - venv = VirtualEnv(ctx=self.context, **venv_config) |
559 | | - else: |
560 | | - venv = nullcontext() |
561 | | - with venv: |
562 | | - try: |
563 | | - importlib.import_module(module_name) |
564 | | - except ImportError as exc: |
565 | | - if os.environ.get("TOOLS_IGNORE_IMPORT_ERRORS", "0") == "0": |
566 | | - self.context.warn( |
567 | | - f"Could not import the registered tools module {module_name!r}: {exc}" |
568 | | - ) |
| 447 | + default_venv: VirtualEnv | AbstractContextManager[None] |
| 448 | + default_venv_config = DefaultVirtualEnv().venv_config |
| 449 | + if default_venv_config: |
| 450 | + if "name" not in default_venv_config: |
| 451 | + default_venv_config["name"] = "default" |
| 452 | + default_venv_config["system_site_packages"] = True |
| 453 | + default_venv_config["add_as_extra_site_packages"] = True |
| 454 | + default_venv = VirtualEnv(ctx=self.context, **default_venv_config) |
| 455 | + else: |
| 456 | + default_venv = nullcontext() |
| 457 | + with default_venv: |
| 458 | + for module_name, venv_config in RegisteredImports(): |
| 459 | + venv: VirtualEnv | AbstractContextManager[None] |
| 460 | + if venv_config: |
| 461 | + if "name" not in venv_config: |
| 462 | + venv_config["name"] = module_name |
| 463 | + venv = VirtualEnv(ctx=self.context, **venv_config) |
| 464 | + else: |
| 465 | + venv = nullcontext() |
| 466 | + with venv: |
| 467 | + try: |
| 468 | + importlib.import_module(module_name) |
| 469 | + except ImportError as exc: |
| 470 | + if os.environ.get("TOOLS_IGNORE_IMPORT_ERRORS", "0") == "0": |
| 471 | + self.context.warn( |
| 472 | + f"Could not import the registered tools module {module_name!r}: {exc}" |
| 473 | + ) |
569 | 474 |
|
570 | 475 | def parse_args(self) -> None: |
571 | 476 | """ |
|
0 commit comments