From 3f09af376871fa810a025aecee2184ee20609b00 Mon Sep 17 00:00:00 2001 From: Olivier Philippon Date: Wed, 4 May 2022 10:02:00 +0100 Subject: [PATCH] [API] Start accepting PathLike objects here and there --- e2e_tests/test_apps/basic.py | 7 +++++-- sandbox/basic.py | 8 ++++++-- src/textual/app.py | 9 +++++---- src/textual/css/parse.py | 3 ++- src/textual/css/stylesheet.py | 19 +++++++++++-------- src/textual/css/tokenize.py | 3 ++- src/textual/css/tokenizer.py | 5 +++-- src/textual/file_monitor.py | 4 +++- 8 files changed, 37 insertions(+), 21 deletions(-) diff --git a/e2e_tests/test_apps/basic.py b/e2e_tests/test_apps/basic.py index a4816fe1c..ff0e0cd68 100644 --- a/e2e_tests/test_apps/basic.py +++ b/e2e_tests/test_apps/basic.py @@ -141,9 +141,12 @@ class BasicApp(App): self.panic(self.tree) -css_file = Path(__file__).parent / "basic.css" +sandbox_folder = Path(__file__).parent app = BasicApp( - css_file=str(css_file), watch_css=True, log="textual.log", log_verbosity=0 + css_file=sandbox_folder / "basic.css", + watch_css=True, + log=sandbox_folder / "textual.log", + log_verbosity=0, ) if __name__ == "__main__": diff --git a/sandbox/basic.py b/sandbox/basic.py index 12cb26211..a9813f9d1 100644 --- a/sandbox/basic.py +++ b/sandbox/basic.py @@ -141,8 +141,12 @@ class BasicApp(App): self.panic(self.tree) -css_file = Path(__file__).parent / "basic.css" -app = BasicApp(css_file=str(css_file), watch_css=True, log="textual.log") +sandbox_folder = Path(__file__).parent +app = BasicApp( + css_file=sandbox_folder / "basic.css", + watch_css=True, + log=sandbox_folder / "textual.log", +) if __name__ == "__main__": app.run() diff --git a/src/textual/app.py b/src/textual/app.py index c909740da..234893830 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -7,6 +7,7 @@ import platform import sys import warnings from contextlib import redirect_stdout +from pathlib import PurePath from time import perf_counter from typing import ( Any, @@ -105,20 +106,20 @@ class App(Generic[ReturnType], DOMNode): def __init__( self, driver_class: Type[Driver] | None = None, - log: str = "", + log: str | PurePath = "", log_verbosity: int = 1, title: str = "Textual Application", - css_file: str | None = None, + css_file: str | PurePath | None = None, watch_css: bool = True, ): """Textual application base class Args: driver_class (Type[Driver] | None, optional): Driver class or ``None`` to auto-detect. Defaults to None. - log (str, optional): Path to log file, or "" to disable. Defaults to "". + log (str | PurePath, optional): Path to log file, or "" to disable. Defaults to "". log_verbosity (int, optional): Log verbosity from 0-3. Defaults to 1. title (str, optional): Default title of the application. Defaults to "Textual Application". - css_file (str | None, optional): Path to CSS or ``None`` for no CSS file. Defaults to None. + css_file (str | PurePath | None, optional): Path to CSS or ``None`` for no CSS file. Defaults to None. watch_css (bool, optional): Watch CSS for changes. Defaults to True. """ # N.B. This must be done *before* we call the parent constructor, because MessagePump's diff --git a/src/textual/css/parse.py b/src/textual/css/parse.py index 6cb47076a..f3ce8d3f7 100644 --- a/src/textual/css/parse.py +++ b/src/textual/css/parse.py @@ -1,6 +1,7 @@ from __future__ import annotations from functools import lru_cache +from pathlib import PurePath from typing import Iterator, Iterable from rich import print @@ -305,7 +306,7 @@ def substitute_references( def parse( - css: str, path: str, variables: dict[str, str] | None = None + css: str, path: str | PurePath, variables: dict[str, str] | None = None ) -> Iterable[RuleSet]: """Parse CSS by tokenizing it, performing variable substitution, and generating rule sets from it. diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index fc98fb901..d4831998b 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -3,7 +3,7 @@ from __future__ import annotations import os from collections import defaultdict from operator import itemgetter -from pathlib import Path +from pathlib import Path, PurePath from typing import cast, Iterable import rich.repr @@ -149,13 +149,13 @@ class Stylesheet: """ self.variables = variables - def _parse_rules(self, css: str, path: str) -> list[RuleSet]: + def _parse_rules(self, css: str, path: str | PurePath) -> list[RuleSet]: """Parse CSS and return rules. Args: css (str): String containing Textual CSS. - path (str): Path to CSS or unique identifier + path (str | PurePath): Path to CSS or unique identifier Raises: StylesheetError: If the CSS is invalid. @@ -171,11 +171,11 @@ class Stylesheet: raise StylesheetError(f"failed to parse css; {error}") return rules - def read(self, filename: str) -> None: + def read(self, filename: str | PurePath) -> None: """Read Textual CSS file. Args: - filename (str): filename of CSS. + filename (str | PurePath): filename of CSS. Raises: StylesheetError: If the CSS could not be read. @@ -188,15 +188,16 @@ class Stylesheet: path = os.path.abspath(filename) except Exception as error: raise StylesheetError(f"unable to read {filename!r}; {error}") - self.source[path] = css + self.source[str(path)] = css self._require_parse = True - def add_source(self, css: str, path: str | None = None) -> None: + def add_source(self, css: str, path: str | PurePath | None = None) -> None: """Parse CSS from a string. Args: css (str): String with CSS source. - path (str, optional): The path of the source if a file, or some other identifier. Defaults to "". + path (str | PurePath, optional): The path of the source if a file, or some other identifier. + Defaults to None. Raises: StylesheetError: If the CSS could not be read. @@ -205,6 +206,8 @@ class Stylesheet: if path is None: path = str(hash(css)) + elif isinstance(path, PurePath): + path = str(css) if path in self.source and self.source[path] == css: # Path already in source, and CSS is identical return diff --git a/src/textual/css/tokenize.py b/src/textual/css/tokenize.py index 7076d390a..dff909c4b 100644 --- a/src/textual/css/tokenize.py +++ b/src/textual/css/tokenize.py @@ -1,6 +1,7 @@ from __future__ import annotations import re +from pathlib import PurePath from typing import Iterable from textual.css.tokenizer import Expect, Tokenizer, Token @@ -136,7 +137,7 @@ class TokenizerState: "declaration_set_end": expect_root_scope, } - def __call__(self, code: str, path: str) -> Iterable[Token]: + def __call__(self, code: str, path: str | PurePath) -> Iterable[Token]: tokenizer = Tokenizer(code, path=path) expect = self.EXPECT get_token = tokenizer.get_token diff --git a/src/textual/css/tokenizer.py b/src/textual/css/tokenizer.py index c77d1d50e..958d1ca32 100644 --- a/src/textual/css/tokenizer.py +++ b/src/textual/css/tokenizer.py @@ -1,6 +1,7 @@ from __future__ import annotations import re +from pathlib import PurePath from typing import NamedTuple from rich.console import Group, RenderableType @@ -148,8 +149,8 @@ class Token(NamedTuple): class Tokenizer: - def __init__(self, text: str, path: str = "") -> None: - self.path = path + def __init__(self, text: str, path: str | PurePath = "") -> None: + self.path = str(path) self.code = text self.lines = text.splitlines(keepends=True) self.line_no = 0 diff --git a/src/textual/file_monitor.py b/src/textual/file_monitor.py index 58957a610..4b20e2981 100644 --- a/src/textual/file_monitor.py +++ b/src/textual/file_monitor.py @@ -1,4 +1,6 @@ +from __future__ import annotations import os +from pathlib import PurePath from typing import Callable import rich.repr @@ -8,7 +10,7 @@ from ._callback import invoke @rich.repr.auto class FileMonitor: - def __init__(self, path: str, callback: Callable) -> None: + def __init__(self, path: str | PurePath, callback: Callable) -> None: self.path = path self.callback = callback self._modified = self._get_modified()