File: config.py

package info (click to toggle)
pytest-watcher 0.4.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 856 kB
  • sloc: python: 925; makefile: 28; sh: 13
file content (91 lines) | stat: -rw-r--r-- 2,412 bytes parent folder | download
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
from argparse import Namespace
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Mapping, Optional

from .constants import DEFAULT_DELAY

try:
    import tomllib
except ImportError:
    import tomli as tomllib


CONFIG_SECTION_NAME = "pytest-watcher"
CLI_FIELDS = {"now", "clear", "delay", "runner", "patterns", "ignore_patterns"}
CONFIG_FIELDS = CLI_FIELDS | {"runner_args"}


@dataclass
class Config:
    path: Path
    now: bool = False
    clear: bool = False
    delay: float = DEFAULT_DELAY
    runner: str = "pytest"
    runner_args: List[str] = field(default_factory=list)
    patterns: List[str] = field(default_factory=list)
    ignore_patterns: List[str] = field(default_factory=list)

    @classmethod
    def create(
        cls, namespace: Namespace, extra_args: Optional[List[str]] = None
    ) -> "Config":
        instance = cls(path=namespace.path)

        config_path = find_config(namespace.path)
        if config_path:
            parsed = parse_config(config_path)
            instance._update_from_mapping(parsed)

        instance._update_from_namespace(namespace, extra_args or [])
        return instance

    def _update_from_mapping(self, data: Mapping):
        for key, val in data.items():
            setattr(self, key, val)

    def _update_from_namespace(
        self, namespace: Namespace, runner_args: Optional[List[str]]
    ):
        self.path = namespace.path

        for f in CLI_FIELDS:
            val = getattr(namespace, f)
            if val:
                setattr(self, f, val)

        if runner_args:
            self.runner_args = runner_args


def find_config(cwd: Path) -> Optional[Path]:
    filename = "pyproject.toml"

    for path in (cwd, *cwd.parents):
        config_path = path.joinpath(filename)

        if config_path.exists():
            return config_path

    return None


def parse_config(path: Path) -> Mapping:
    with open(path, "rb") as f:
        try:
            data = tomllib.load(f)
        except Exception as exc:
            raise SystemExit(f"Error parsing pyproject.toml\n{exc}")

    try:
        data = data["tool"][CONFIG_SECTION_NAME]
    except KeyError:
        return {}

    for key in data.keys():
        if key not in CONFIG_FIELDS:
            raise SystemExit(
                f"Error parsing pyproject.toml.\nUnrecognized option: {key}"
            )
    return data