## hatch_ci._support

src/hatch_ci/_support.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    # This file is not mean to be used!!!
    # ONLY FOR INTERNAL DEBUG

    # wraps each method in a derived class of:
    # - VersionSourceInterface
    # - BuildHookInterface
    # etc.

    # wraps this:
    # site-packages\pyproject_hooks\_impl.py ->
    # def _call_hook(self, hook_name, kwargs):
    #   ...
    #   from hatch_ci._support import tracer
    #   with tracer.decorator(_in_proc_script_path)() as script:

    # change the OUTPUT below
    from __future__ import annotations

    import functools
    import inspect
    import io
    import json
    import os
    import sys
    from pathlib import Path
    from typing import Any

    OUTPUT = None  # eg. r"C:\Users\antonio\Projects\github\hatch-ci\NOTES.txt"

    def is_scm(path: Path) -> str:
        return "<git>" if (Path(path) / ".git").exists() else "<plain>"

    def jformat(data: Any, pre: str) -> str:
        return json.dumps(data, indent=2, sort_keys=True).replace(
            "\n", f"\n{' ' * len(pre)}"
        )

    class Tracer:
        def __init__(self, path: Path | str | None = None):
            self.path = Path(path) if path else None

        def decorator(self, fn):
            if fn.__name__ == "_in_proc_script_path":
                return self.wraps__in_proc_script_path(fn)
            else:
                return self.wraps_interface(fn)

        def append(self, txt):
            if not self.path:
                return
            with self.path.open("a") as fp:
                fp.write(txt.getvalue() if hasattr(txt, "getvalue") else txt)

        def wraps__in_proc_script_path(self, fn):
            # this is mean to wrap the _in_proc_script_path function
            @functools.wraps(fn)
            def _fn(*args, **kwargs):
                # ok, looking to wrap _call_hook in _impl.py
                frames = [
                    f
                    for f in inspect.getouterframes(inspect.currentframe())
                    if "_impl.py" in f.filename and f.function == "_call_hook"
                ]
                if len(frames) != 1:
                    raise RuntimeError("cannot find _call_hook in _impl.py frame")
                variables = frames[0].frame.f_locals

                if not self.path:
                    fp = io.StringIO()
                    print("", file=fp)
                    print(f"[pid={os.getpid()}]", file=fp)
                    print(f"<hook> '{variables['hook_name']}'", file=fp)
                    scm = (
                        "<git>"
                        if (Path(variables["self"].source_dir) / ".git").exists()
                        else "<plain>"
                    )
                    print(
                        f"  cwd (self.source_dir): {variables['self'].source_dir} {scm}",
                        file=fp,
                    )
                    print(f"  kwargs: {variables['kwargs']}", file=fp)
                    print(f"  extra_environ: {variables['extra_environ']}", file=fp)
                    print("  input.json:", file=fp)
                    pre = "  input.json: "
                    print(f"{pre}{jformat(variables['hook_input'], pre=pre)}", file=fp)
                    self.append(fp)

                return fn(*args, **kwargs)

            return _fn

        def wraps_interface(self, method):
            # a method decorator
            # you should wrap methods of subclasses of:
            # - VersionSourceInterface
            # - BuildHookInterface
            # etc.
            @functools.wraps(method)
            def _fn(slf, *args, **kwargs):
                if self.path:
                    fp = io.StringIO()
                    print("", file=fp)
                    print(f"[pid={os.getpid()}] {sys.argv=}", file=fp)
                    print(
                        f"!callback! '{method.__name__}' {slf.__class__.__name__}", file=fp
                    )
                    print(f"  self.root: {slf.root} {is_scm(slf.root)}", file=fp)
                    print(f"  config: {jformat(slf.config, '  config: ')}", file=fp)
                    self.append(fp)
                return method(slf, *args, **kwargs)

            return _fn if self.path else method

    tracer = Tracer()  # r"C:\Users\antonio\Projects\github\hatch-ci\NOTES.txt")