diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b726a7b0c..6467ab5b43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Buttons will now display multiple lines, and have auto height https://github.com/Textualize/textual/pull/3539 - DataTable now has a max-height of 100vh rather than 100%, which doesn't work with auto - Breaking change: empty rules now result in an error https://github.com/Textualize/textual/pull/3566 +- Improved startup time by caching CSS parsing https://github.com/Textualize/textual/pull/3575 ### Added diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 9adbfd6ca4..3362520293 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -2,7 +2,6 @@ import os from collections import defaultdict -from functools import lru_cache from itertools import chain from operator import itemgetter from pathlib import Path, PurePath @@ -17,6 +16,7 @@ from rich.syntax import Syntax from rich.text import Text +from .._cache import LRUCache from ..dom import DOMNode from ..widget import Widget from .errors import StylesheetError @@ -135,6 +135,7 @@ def __init__(self, *, variables: dict[str, str] | None = None) -> None: self.source: dict[str, CssSource] = {} self._require_parse = False self._invalid_css: set[str] = set() + self._parse_cache: LRUCache[tuple, list[RuleSet]] = LRUCache(64) def __rich_repr__(self) -> rich.repr.Result: yield list(self.source.keys()) @@ -196,8 +197,8 @@ def set_variables(self, variables: dict[str, str]) -> None: self._variables = variables self.__variable_tokens = None self._invalid_css = set() + self._parse_cache.clear() - @lru_cache(128) def _parse_rules( self, css: str, @@ -221,6 +222,11 @@ def _parse_rules( Returns: List of RuleSets. """ + cache_key = (css, path, is_default_rules, tie_breaker, scope) + try: + return self._parse_cache[cache_key] + except KeyError: + pass try: rules = list( parse( @@ -237,6 +243,7 @@ def _parse_rules( except Exception as error: raise StylesheetError(f"failed to parse css; {error}") + self._parse_cache[cache_key] = rules return rules def read(self, filename: str | PurePath) -> None: