From 1f68c24828295375d351b1e8064fddd0a4308413 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sun, 8 Sep 2024 21:09:36 +0100 Subject: [PATCH 1/9] Refreshing url variables on dotenv chanes --- pyproject.toml | 1 + src/posting/app.py | 22 ++++++++++- src/posting/variables.py | 33 ++++++++++++----- src/posting/widgets/request/url_bar.py | 27 ++++++++++++-- uv.lock | 51 ++++++++++++++++++++++++++ 5 files changed, 120 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 390c6782..302e6820 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "python-dotenv==1.0.1", "textual[syntax]==0.79.1", "textual-autocomplete==3.0.0a9", + "watchfiles>=0.24.0", ] readme = "README.md" requires-python = ">= 3.11" diff --git a/src/posting/app.py b/src/posting/app.py index f7839beb..c61af233 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -23,6 +23,7 @@ ) from textual.widgets._tabbed_content import ContentTab from textual.widgets.text_area import TextAreaTheme +from watchfiles import awatch from posting.collection import ( Collection, Cookie, @@ -145,9 +146,9 @@ def __init__( self._initial_layout: PostingLayout = layout self.environment_files = environment_files self.settings = SETTINGS.get() - load_variables(self.environment_files, self.settings.use_host_environment) - def on_mount(self) -> None: + async def on_mount(self) -> None: + await load_variables(self.environment_files, self.settings.use_host_environment) self.layout = self._initial_layout # Set the initial focus based on the settings. @@ -628,10 +629,26 @@ def __init__( self.collection = collection self.collection_specified = collection_specified self.animation_level = settings.animation + self.env_changed_signal = Signal[None](self, "env-changed") theme: Reactive[str] = reactive("galaxy", init=False) _jumping: Reactive[bool] = reactive(False, init=False, bindings=True) + @work(exclusive=True, group="environment-watcher") + async def watch_environment_files(self) -> None: + async for changes in awatch(*self.environment_files): + await load_variables( + self.environment_files, + self.settings.use_host_environment, + avoid_cache=True, + ) + self.env_changed_signal.publish(None) + self.notify( + title="Environment changed", + message=f"Reloaded {len(changes)} dotenv files", + timeout=3, + ) + def on_mount(self) -> None: self.jumper = Jumper( { @@ -653,6 +670,7 @@ def on_mount(self) -> None: ) self.theme_change_signal = Signal[Theme](self, "theme-changed") self.theme = self.settings.theme + self.watch_environment_files() def get_default_screen(self) -> MainScreen: self.main_screen = MainScreen( diff --git a/src/posting/variables.py b/src/posting/variables.py index 35d67710..4c6272fd 100644 --- a/src/posting/variables.py +++ b/src/posting/variables.py @@ -6,31 +6,45 @@ import os from pathlib import Path from dotenv import dotenv_values +from asyncio import Lock _VARIABLES_PATTERN = re.compile( r"\$(?:([a-zA-Z_][a-zA-Z0-9_]*)|{([a-zA-Z_][a-zA-Z0-9_]*)})" ) -_initial_variables: dict[str, str | None] = {} -VARIABLES: ContextVar[dict[str, str | None]] = ContextVar( - "variables", default=_initial_variables -) + +class SharedVariables: + def __init__(self): + self._variables: dict[str, str | None] = {} + self._lock = Lock() + + def get(self) -> dict[str, str | None]: + return self._variables.copy() + + async def set(self, variables: dict[str, str | None]) -> None: + async with self._lock: + self._variables = variables + + +VARIABLES = SharedVariables() def get_variables() -> dict[str, str | None]: return VARIABLES.get() -def load_variables( - environment_files: tuple[Path, ...], use_host_environment: bool +async def load_variables( + environment_files: tuple[Path, ...], + use_host_environment: bool, + avoid_cache: bool = False, ) -> dict[str, str | None]: """Load the variables that are currently available in the environment. This will make them available via the `get_variables` function.""" - existing_variables = VARIABLES.get() - if existing_variables: + existing_variables = get_variables() + if existing_variables and not avoid_cache: return {key: value for key, value in existing_variables} variables: dict[str, str | None] = { @@ -42,7 +56,7 @@ def load_variables( host_env_variables = {key: value for key, value in os.environ.items()} variables = {**variables, **host_env_variables} - VARIABLES.set(variables) + await VARIABLES.set(variables) return variables @@ -162,6 +176,7 @@ def get_variable_at_cursor(cursor: int, text: str) -> str | None: return text[start:end] +@lru_cache() def extract_variable_name(variable_text: str) -> str: """ Extract the variable name from a variable reference. diff --git a/src/posting/widgets/request/url_bar.py b/src/posting/widgets/request/url_bar.py index 2c4ee6b7..d248f03d 100644 --- a/src/posting/widgets/request/url_bar.py +++ b/src/posting/widgets/request/url_bar.py @@ -152,6 +152,10 @@ def __init__( self.cached_base_urls: list[str] = [] self._trace_events: set[Event] = set() + def on_env_changed(self, _: None) -> None: + self._display_variable_at_cursor() + self.url_input.refresh() + def compose(self) -> ComposeResult: with Horizontal(): yield MethodSelector(id="method-selector") @@ -161,6 +165,7 @@ def compose(self) -> ComposeResult: ) yield Label(id="trace-markers") yield SendRequestButton("Send") + variable_value_bar = Label(id="variable-value-bar") if SETTINGS.get().url_bar.show_value_preview: yield variable_value_bar @@ -175,19 +180,35 @@ def on_mount(self) -> None: self.on_theme_change(self.app.themes[self.app.theme]) self.app.theme_change_signal.subscribe(self, self.on_theme_change) + self.app.env_changed_signal.subscribe(self, self.on_env_changed) @on(Input.Changed) def on_change(self, event: Input.Changed) -> None: - self.variable_value_bar.update("") + try: + self.variable_value_bar.update("") + except NoMatches: + return @on(UrlInput.Blurred) def on_blur(self, event: UrlInput.Blurred) -> None: - self.variable_value_bar.update("") + try: + self.variable_value_bar.update("") + except NoMatches: + return @on(UrlInput.CursorMoved) def on_cursor_moved(self, event: UrlInput.CursorMoved) -> None: + self._display_variable_at_cursor() + + def _display_variable_at_cursor(self) -> None: + url_input = self.url_input + + cursor_position = url_input.cursor_position + value = url_input.value + variable_at_cursor = get_variable_at_cursor(cursor_position, value) + variables = get_variables() - variable_at_cursor = get_variable_at_cursor(event.cursor_position, event.value) + print("got variables", variables) try: variable_bar = self.variable_value_bar except NoMatches: diff --git a/uv.lock b/uv.lock index 1071b057..2802a3f8 100644 --- a/uv.lock +++ b/uv.lock @@ -786,6 +786,7 @@ dependencies = [ { name = "pyyaml" }, { name = "textual", extra = ["syntax"] }, { name = "textual-autocomplete" }, + { name = "watchfiles" }, { name = "xdg-base-dirs" }, ] @@ -813,6 +814,7 @@ requires-dist = [ { name = "pyyaml", specifier = "==6.0.2" }, { name = "textual", extras = ["syntax"], specifier = "==0.79.1" }, { name = "textual-autocomplete", specifier = "==3.0.0a9" }, + { name = "watchfiles", specifier = ">=0.24.0" }, { name = "xdg-base-dirs", specifier = "==6.0.1" }, ] @@ -1351,6 +1353,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/0b/43b96a9ecdd65ff5545b1b13b687ca486da5c6249475b1a45f24d63a1858/watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c", size = 82933 }, ] +[[package]] +name = "watchfiles" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/27/2ba23c8cc85796e2d41976439b08d52f691655fdb9401362099502d1f0cf/watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1", size = 37870 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/02/366ae902cd81ca5befcd1854b5c7477b378f68861597cef854bd6dc69fbe/watchfiles-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428", size = 375579 }, + { url = "https://files.pythonhosted.org/packages/bc/67/d8c9d256791fe312fea118a8a051411337c948101a24586e2df237507976/watchfiles-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c", size = 367726 }, + { url = "https://files.pythonhosted.org/packages/b1/dc/a8427b21ef46386adf824a9fec4be9d16a475b850616cfd98cf09a97a2ef/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43", size = 437735 }, + { url = "https://files.pythonhosted.org/packages/3a/21/0b20bef581a9fbfef290a822c8be645432ceb05fb0741bf3c032e0d90d9a/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327", size = 433644 }, + { url = "https://files.pythonhosted.org/packages/1c/e8/d5e5f71cc443c85a72e70b24269a30e529227986096abe091040d6358ea9/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5", size = 450928 }, + { url = "https://files.pythonhosted.org/packages/61/ee/bf17f5a370c2fcff49e1fec987a6a43fd798d8427ea754ce45b38f9e117a/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61", size = 469072 }, + { url = "https://files.pythonhosted.org/packages/a3/34/03b66d425986de3fc6077e74a74c78da298f8cb598887f664a4485e55543/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15", size = 475517 }, + { url = "https://files.pythonhosted.org/packages/70/eb/82f089c4f44b3171ad87a1b433abb4696f18eb67292909630d886e073abe/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823", size = 425480 }, + { url = "https://files.pythonhosted.org/packages/53/20/20509c8f5291e14e8a13104b1808cd7cf5c44acd5feaecb427a49d387774/watchfiles-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab", size = 612322 }, + { url = "https://files.pythonhosted.org/packages/df/2b/5f65014a8cecc0a120f5587722068a975a692cadbe9fe4ea56b3d8e43f14/watchfiles-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec", size = 595094 }, + { url = "https://files.pythonhosted.org/packages/18/98/006d8043a82c0a09d282d669c88e587b3a05cabdd7f4900e402250a249ac/watchfiles-0.24.0-cp311-none-win32.whl", hash = "sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d", size = 264191 }, + { url = "https://files.pythonhosted.org/packages/8a/8b/badd9247d6ec25f5f634a9b3d0d92e39c045824ec7e8afcedca8ee52c1e2/watchfiles-0.24.0-cp311-none-win_amd64.whl", hash = "sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c", size = 277527 }, + { url = "https://files.pythonhosted.org/packages/af/19/35c957c84ee69d904299a38bae3614f7cede45f07f174f6d5a2f4dbd6033/watchfiles-0.24.0-cp311-none-win_arm64.whl", hash = "sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633", size = 266253 }, + { url = "https://files.pythonhosted.org/packages/35/82/92a7bb6dc82d183e304a5f84ae5437b59ee72d48cee805a9adda2488b237/watchfiles-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a", size = 374137 }, + { url = "https://files.pythonhosted.org/packages/87/91/49e9a497ddaf4da5e3802d51ed67ff33024597c28f652b8ab1e7c0f5718b/watchfiles-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370", size = 367733 }, + { url = "https://files.pythonhosted.org/packages/0d/d8/90eb950ab4998effea2df4cf3a705dc594f6bc501c5a353073aa990be965/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6", size = 437322 }, + { url = "https://files.pythonhosted.org/packages/6c/a2/300b22e7bc2a222dd91fce121cefa7b49aa0d26a627b2777e7bdfcf1110b/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b", size = 433409 }, + { url = "https://files.pythonhosted.org/packages/99/44/27d7708a43538ed6c26708bcccdde757da8b7efb93f4871d4cc39cffa1cc/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e", size = 452142 }, + { url = "https://files.pythonhosted.org/packages/b0/ec/c4e04f755be003129a2c5f3520d2c47026f00da5ecb9ef1e4f9449637571/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea", size = 469414 }, + { url = "https://files.pythonhosted.org/packages/c5/4e/cdd7de3e7ac6432b0abf282ec4c1a1a2ec62dfe423cf269b86861667752d/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f", size = 472962 }, + { url = "https://files.pythonhosted.org/packages/27/69/e1da9d34da7fc59db358424f5d89a56aaafe09f6961b64e36457a80a7194/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234", size = 425705 }, + { url = "https://files.pythonhosted.org/packages/e8/c1/24d0f7357be89be4a43e0a656259676ea3d7a074901f47022f32e2957798/watchfiles-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef", size = 612851 }, + { url = "https://files.pythonhosted.org/packages/c7/af/175ba9b268dec56f821639c9893b506c69fd999fe6a2e2c51de420eb2f01/watchfiles-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968", size = 594868 }, + { url = "https://files.pythonhosted.org/packages/44/81/1f701323a9f70805bc81c74c990137123344a80ea23ab9504a99492907f8/watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444", size = 264109 }, + { url = "https://files.pythonhosted.org/packages/b4/0b/32cde5bc2ebd9f351be326837c61bdeb05ad652b793f25c91cac0b48a60b/watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896", size = 277055 }, + { url = "https://files.pythonhosted.org/packages/4b/81/daade76ce33d21dbec7a15afd7479de8db786e5f7b7d249263b4ea174e08/watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418", size = 266169 }, + { url = "https://files.pythonhosted.org/packages/30/dc/6e9f5447ae14f645532468a84323a942996d74d5e817837a5c8ce9d16c69/watchfiles-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48", size = 373764 }, + { url = "https://files.pythonhosted.org/packages/79/c0/c3a9929c372816c7fc87d8149bd722608ea58dc0986d3ef7564c79ad7112/watchfiles-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90", size = 367873 }, + { url = "https://files.pythonhosted.org/packages/2e/11/ff9a4445a7cfc1c98caf99042df38964af12eed47d496dd5d0d90417349f/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94", size = 438381 }, + { url = "https://files.pythonhosted.org/packages/48/a3/763ba18c98211d7bb6c0f417b2d7946d346cdc359d585cc28a17b48e964b/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e", size = 432809 }, + { url = "https://files.pythonhosted.org/packages/30/4c/616c111b9d40eea2547489abaf4ffc84511e86888a166d3a4522c2ba44b5/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827", size = 451801 }, + { url = "https://files.pythonhosted.org/packages/b6/be/d7da83307863a422abbfeb12903a76e43200c90ebe5d6afd6a59d158edea/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df", size = 468886 }, + { url = "https://files.pythonhosted.org/packages/1d/d3/3dfe131ee59d5e90b932cf56aba5c996309d94dafe3d02d204364c23461c/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab", size = 472973 }, + { url = "https://files.pythonhosted.org/packages/42/6c/279288cc5653a289290d183b60a6d80e05f439d5bfdfaf2d113738d0f932/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f", size = 425282 }, + { url = "https://files.pythonhosted.org/packages/d6/d7/58afe5e85217e845edf26d8780c2d2d2ae77675eeb8d1b8b8121d799ce52/watchfiles-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b", size = 612540 }, + { url = "https://files.pythonhosted.org/packages/6d/d5/b96eeb9fe3fda137200dd2f31553670cbc731b1e13164fd69b49870b76ec/watchfiles-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18", size = 593625 }, + { url = "https://files.pythonhosted.org/packages/c1/e5/c326fe52ee0054107267608d8cea275e80be4455b6079491dfd9da29f46f/watchfiles-0.24.0-cp313-none-win32.whl", hash = "sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07", size = 263899 }, + { url = "https://files.pythonhosted.org/packages/a6/8b/8a7755c5e7221bb35fe4af2dc44db9174f90ebf0344fd5e9b1e8b42d381e/watchfiles-0.24.0-cp313-none-win_amd64.whl", hash = "sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366", size = 276622 }, +] + [[package]] name = "xdg-base-dirs" version = "6.0.1" From 01e70399570f87c8841f694d4fe8e33f8f637979 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sun, 8 Sep 2024 21:16:20 +0100 Subject: [PATCH 2/9] Ensure variables are loaded first --- src/posting/__main__.py | 3 +++ src/posting/app.py | 1 - src/posting/widgets/variable_autocomplete.py | 8 +++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/posting/__main__.py b/src/posting/__main__.py index 550477c6..94a3fa39 100644 --- a/src/posting/__main__.py +++ b/src/posting/__main__.py @@ -1,3 +1,4 @@ +import asyncio from pathlib import Path import click @@ -13,6 +14,7 @@ default_collection_directory, theme_directory, ) +from posting.variables import load_variables def create_config_file() -> None: @@ -132,5 +134,6 @@ def make_posting( env_paths = tuple(Path(e).resolve() for e in env) settings = Settings(_env_file=env_paths) # type: ignore[call-arg] + asyncio.run(load_variables(env_paths, settings.use_host_environment)) return Posting(settings, env_paths, collection_tree, not using_default_collection) diff --git a/src/posting/app.py b/src/posting/app.py index c61af233..c2d21891 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -148,7 +148,6 @@ def __init__( self.settings = SETTINGS.get() async def on_mount(self) -> None: - await load_variables(self.environment_files, self.settings.use_host_environment) self.layout = self._initial_layout # Set the initial focus based on the settings. diff --git a/src/posting/widgets/variable_autocomplete.py b/src/posting/widgets/variable_autocomplete.py index 168d0594..55593b13 100644 --- a/src/posting/widgets/variable_autocomplete.py +++ b/src/posting/widgets/variable_autocomplete.py @@ -46,11 +46,13 @@ def __init__( classes, disabled, ) - if variable_candidates is None: - variable_candidates = [ + self.variable_candidates = variable_candidates + + def on_mount(self) -> None: + if self.variable_candidates is None: + self.variable_candidates = [ DropdownItem(main=f"${variable}") for variable in get_variables() ] - self.variable_candidates = variable_candidates def get_candidates(self, target_state: TargetState) -> list[DropdownItem]: cursor = target_state.selection.end[1] From e389048076822b154d14d6edbdb5b0a6fa806a9c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sun, 8 Sep 2024 21:18:53 +0100 Subject: [PATCH 3/9] Remove debugging code --- src/posting/widgets/request/url_bar.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/posting/widgets/request/url_bar.py b/src/posting/widgets/request/url_bar.py index d248f03d..42f06ae2 100644 --- a/src/posting/widgets/request/url_bar.py +++ b/src/posting/widgets/request/url_bar.py @@ -208,7 +208,6 @@ def _display_variable_at_cursor(self) -> None: variable_at_cursor = get_variable_at_cursor(cursor_position, value) variables = get_variables() - print("got variables", variables) try: variable_bar = self.variable_value_bar except NoMatches: From 44dd509d5ed33d7bd5b7057d2056b677fe597000 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sun, 8 Sep 2024 21:35:42 +0100 Subject: [PATCH 4/9] Add config for showing/hiding collection browser ons tartup --- .coverage | Bin 53248 -> 53248 bytes docs/CHANGELOG.md | 5 +++++ docs/guide/configuration.md | 1 + docs/guide/index.md | 2 +- src/posting/app.py | 13 +++++-------- src/posting/config.py | 3 +++ 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.coverage b/.coverage index d02e6dc7fdea4c657be3fb3c241bc51d1a82adf7..429d02f815abbdedd7cfd36bcf72fe88084527d0 100644 GIT binary patch delta 1775 zcmY+EdrVVT7{Jf{Zt3gxwzWLk0)0@RAP5wSY={CXTQZ|=nTs(_!Nk{=2)ZSECU9He zs8d9=&gpQ7&b{2`10rUV(K_R@DKeM+Aq%gvCEzksw21;j@7?KH`p3>6=l6ZT?>i^= z+;dw`C+q2C+ftasBdIHy#LHXG2m*H=EmpTN+*$4r*TlW9Yg4yzHM%-(z3vrVA@>4T zp)+zN+IcRWi`V|lskE=D$F=Ktkq`6Z{O{^P{s+F9-=-em*Yj0;0iVemct$g>xuxmW zoYNfDys6o#Db+YN3NFGu;wHIat{eStbldcEDxu7~QO1-tR*|V`E18;2GDcN~fz0p9 z#TsYe5j;EjUdJ;H`DmcKwx@RMt8^1PLY1hhWAapm{{}kl0-?GTkDpj$>caL zT34wRn2pvIbzsDZL@rsK&EP?|# zW@TJ+y-cOjMh~lERBIQHI&q;--;yO@7il&WUyzgvToB#C9oDIEK8X^F^8Z361Ey$#QWHQ5*6_$k$+9tDKvk@~rJrt0z&a7I*T zf8WA()Gd#`1~L-%Sa3SgOS8lQj-5&J59CbZyQ6^#kIaS} z#~xTYY$3*!R&5O)46qiQLb9bcaXWdWhijy%NXk#-K>{R;_;=4NEy2m8Ugx`UsHiF) zn@OB)5$mnNnudxN7Cf`h**R>C?w69!1}hlQ;v|w;lb@PVr^Eo`R^db<63o@**Nb1q z3BH`xbw(Udl+!5oU$G7TdOI}PG&UW#887hlwq_n#9?w+z9=B!)g;+|J` zoIx^F9bk=EO-e4UczanEw;`Y*2aJvrO>+5At(dN2Epu^rJwE8}^5z%D}uy&f$g`nNLA z=CMa>EJ|`CcdwQPL!r7589U~u`~JSyb$7SIBMBq-_8E>bzQ{{jT$~5?d(gy9Z7OMasQ)hvbl&NrnG8vA?dd%=4RVFw_X#^i-A{>deBtRQg z@o<>Z0IigIXo36I6%q62T`s74)(=*)UcPt? delta 1876 zcmb7^Sx{3~7{|}emxUxZxhX~=EMb#HWR+#qQi68c2bZ>jiVK^dpzJDwsN_b`6K6!K zlNqLt7U}X}YsG~TYf*w@sZ|R0p;PT3A-1E`jt!JnSyJxp$+67zwfE)so!|GrIp==K zjin7NZE(V$F`Nu|lQCQ__=x8bLMf^ihLx#W)YEFGdY3v`9nCq_A>23ICu+S~#T`;J zT#3rXWpiPiL3>?$QhPufsr6S4X<5||nirZInlq|CO|#~JrbLsgS)o~?I>Nx)Ubo1zM>-+xVZQcD$-M`m}ePXq} zFbR%rHnE{axXcFL1^M$B*Q=Al!3U8*tAGtKn;_% z2T5lj$Z_2V84(g5Ba2&(t4UuQd_|7SJw*Vc#igV$`b`^9o5)C*Bpu#DMCfZmuWpqX zo0FG5!OL;JC(JPloJ$IwX2o0;&LP!2_FmS~5UCt*Bnw`vR4H(_C!na5ypGqCYOh?# zFaGuq$>Ndv);e<>8tAC#tSH(%AsK5V+m$b~_qL9lG1+gEgQ5bczhSx}y zN-Yy%l@aw$ibHCVA_)p6M<*L2Wa~TTU1XJd;>qozm%<99)Fq8`N~?ra`bBZSph<<5 z@z}(1MSvBNYH5ON;#4Ae{9oPf-t!||eVU?wXsC&`=rB*(q4DW}kbLH5{B=#r0$vvp|?Bc^mcchld*6b)&Ah zbUofne00=TO~AKSE<>I#4IzEC7S|GQjBps3p1%w4PO5cy4-ui^FZ)2aJB-8|Qw9aD zAxbxYfbqt=Nfi-h^tvR9V*AA)RBaCA@Gc^QL(RyJ{M`-{%@>9G;Mc<6F!9(#swG<7GKRhd5yOsKYsS!Iv zCb=oQI4aKY;;=YD{x3u~ufc`no&fLKxWVTy-i`P2#RWF8*_SusJmS-}Lf;#a>J2?C z8HVTjKyVC0wOQ=i7mPQPz;d57M@)BR7+RumTvcK#?Y7-m68OoP!R{_*-R`nP!$6QC>O$h$^}qA8;OFwR7FA^Wdzhx&WD=WNEqy< zDin56hQLnBAgG#+7 ComposeResult: yield AppHeader() yield UrlBar() with AppBody(): - yield CollectionBrowser(collection=self.collection) + collection_browser = CollectionBrowser(collection=self.collection) + collection_browser.display = ( + self.settings.collection_browser.show_on_startup + ) + yield collection_browser yield RequestEditor() yield ResponseArea() yield Footer(show_command_palette=False) @@ -677,13 +681,6 @@ def get_default_screen(self) -> MainScreen: layout=self.settings.layout, environment_files=self.environment_files, ) - if not self.collection_specified: - self.notify( - "Using the default collection directory.", - title="No collection specified", - severity="warning", - timeout=7, - ) return self.main_screen def get_css_variables(self) -> dict[str, str]: diff --git a/src/posting/config.py b/src/posting/config.py index ae0b2cf1..c889965e 100644 --- a/src/posting/config.py +++ b/src/posting/config.py @@ -89,6 +89,9 @@ class CollectionBrowserSettings(BaseModel): position: Literal["left", "right"] = Field(default="left") """The position of the collection browser on screen.""" + show_on_startup: bool = Field(default=True) + """If enabled, the collection browser will be shown on startup.""" + class Settings(BaseSettings): model_config = SettingsConfigDict( From 74031d9ee7af925b677020270ebd404e4a94389a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sun, 8 Sep 2024 21:41:37 +0100 Subject: [PATCH 5/9] Fix command palette for new textual --- src/posting/commands.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/posting/commands.py b/src/posting/commands.py index 8adfd391..7ab1f049 100644 --- a/src/posting/commands.py +++ b/src/posting/commands.py @@ -45,28 +45,28 @@ def commands( # Change the available commands depending on what is currently # maximized on the main screen. - maximized = screen.maximized reset_command = ( "view: reset", - partial(screen.maximize_section, None), + partial(screen.expand_section, None), "Reset section sizes to default", True, ) expand_request_command = ( "view: expand request", - partial(screen.maximize_section, "request"), + partial(screen.expand_section, "request"), "Expand the request section", True, ) expand_response_command = ( "view: expand response", - partial(screen.maximize_section, "response"), + partial(screen.expand_section, "response"), "Expand the response section", True, ) - if maximized == "request": + expanded_section = screen.expanded_section + if expanded_section == "request": commands_to_show.extend([reset_command, expand_response_command]) - elif maximized == "response": + elif expanded_section == "response": commands_to_show.extend([reset_command, expand_request_command]) else: commands_to_show.extend( From dee98b54f7e3b26dfa4cbbaae78bb5c8c725ffa4 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sun, 8 Sep 2024 21:47:11 +0100 Subject: [PATCH 6/9] Watch env files behind config, disable in snapshot tests --- .coverage | Bin 53248 -> 53248 bytes docs/guide/configuration.md | 1 + src/posting/app.py | 3 ++- src/posting/config.py | 3 +++ tests/sample-configs/custom_theme.yaml | 2 ++ tests/sample-configs/custom_theme2.yaml | 1 + tests/sample-configs/general.yaml | 1 + tests/sample-configs/modified_config.yaml | 3 ++- 8 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.coverage b/.coverage index 429d02f815abbdedd7cfd36bcf72fe88084527d0..168997fb871f8c4be1c5755077b0dca5048beaac 100644 GIT binary patch delta 1818 zcmZvcdrVVT9LLY?ue7wc?d>`c%A@636cvl0Lq)37?T@M36yH1r!51$@K~Q>&=pl>9 z+%N{`G|Vi^vdtF^7KfwNh4^41*-J8|g_+rzxPm0eOL}*DT>micBsZVm`TV{)xu?II zTAIMp1jmCJ?METY813ck&v;(`3z{ddLGmtnjUq{*W6!h2@@woWb~*QmyUw+;3%L_) zJXgWxbL+VXHb!+_^@-|;%BYHxUt$AUPNh`-s{EFfD8EpiQ1bHU$^vDYa*;Ah-l+6f zg5sh4siIYJPEjLwD$3=3iUP$l^wMh4t3Q_UMTeI8FtX}&AGZM9n5ko`1)=V()q>tS z;kbI)TE5{~$(Tb3Miy87mMbG)#fgzdt@fPbpJd5!Jk99adCFb<^LGpSU1SxJ=lN*I z<9p3L#b6QmGZ(q&>5z<&(WXZ||17?VdUG^*jhK-cM7I+Y7=-xI+cC@oPUep*h@CaV zJ+HK4*E^Z1W55# zPuA}wC&5{s1jk5mhDRyi@t5FD9wn3O@kUZC@pUEG-!)vj+W4V!pub=Nt_iNMhKOP< z>Iqzg*OSD4i90L*=2KCUv*K*IISzHV6t)%Srj3e*_!@Grf~D5Zv(8UV)*n5W$+irb zg~|7ueU@0D2(No(I`+K(oz4K6&IO-0e)m7#NDld}{c?t5* z8BYLSOJ3M0F4Ai($k>sE(vi)IkmVxli&IHQ1BNpqL#YDJ{< zn;Z8KjfK=AQZF>RMo-0)3;jJg+4;4Tt4NmK>FcXgSk#y&?T9%kfRJSBhpY*yawax6 zB-cgq_eM(Drav~bq4*$)B+oJ@kgJg8oEW#4^4q0#$CxiIgDw|`50HM?OuH7L*asKe z+d+XVi3y5zFR-G?Xz;`P$u@TS_jPc#XesjitO)s)gyM3~xiRi;7RJh|iSE|huLK|+ zLhb=%;U`tNj0AKByOyy%&$%_ms_{M|BBGylf%}#N30KFp8eB@0AyVgiNf3m}4l}Zi z@8j?uBEurg%y8fM@JYuL9gBAp6BwSU>pyCPP+UxQ$$EDld6A)2!r;`TJ*!7*BfBYU z@mTVFTqI^1J~)%9BHP)7nCH<>*gURK} zzZAQ0<$hz!V}^zv2aFAABK!Zkdv9<>!{_zUBAnaMRUdCq;v8~bh^8#={*#gS z=J=}d7P60c&E2V9Da=mKOc~_^adv}HA6THnnWUAXa(69`VK>?oc4nV0>@}UfGhZl32)^HTzLn{1o4VoTm;k()WQ_96 z^cP2w?+}F{)X6~e@ueyrwn#}^q=0lvDQut=!5Yd$I7~SQc*P4rlwnXp84SB9 zr$N4i{G*6qHQmmGBa{hHgOHe(bKwwWJX90%GLaE1RK$VVo0$z&RLz2elr!M~L$Noa6`3_&)$q%+t^*ZEIY9NPF4cXoZ2U%39Ad^xF86LhVVj<0&k;7)%lffoR ZU)V?~f%V>q53Hj~3~MO?QqMIR{{?z_73}~3 delta 1689 zcmY+DdrVVT9LLZ7-O~2g-+0-$Y{h^78((9rENcuCkIlnOpU$ItTj_)-2Q-;Mq^x?QGePA+R$t6|D^R*|W31DTp^8ip&wK<3xA zN{z4Y9y~hwM#m!|KN{$+Jzu-wIoiaHP$jDB7(H6i`y)e?_2H3FeEO-bq{=X>#pz_V z_nn>XvQi5N|mLq-uxBuY2G z>c4UK{*#4z6BbGCnWOZZR>TXcsuxelTFetkGBP^MsjI~0DF&=3QL1oh^}7zKK2qW*{>sB4ndGV5zFH-$*#gG zN$N2FIu!p1AVLl?V!(a2_dp3KtVmJG%(!ktPr4M!$|^$<`fazVnB*ydKXZ z7IVSvY)uBvAz^ubC`enN(T7umFUwnXk!EI3cq|q(;%riP=MCBs%Ds8|yLLvvKBA=M zDy6+>+RIw7m!!G#l-VuIP@mj%tA6^mu{ys6&sF7|ZzF6+-4gZa>it-DWZ*2KmuD*j zJUf}%K=c9E;5Sx8F3pKh*dbvLsXKjxcBXH}(>KYRa3&Ev-UpXi<(DB8zc9t=u$xGx zxdV(DyGZ2Aa_Y~-tvH!TKC5!&oa_2;mt&)i zLla5s@SJFWi+BH$WTrBzw0Hp1qwCviuUEAHS`yzDblf<9t_0mUAD^7bv|_Eg*iilaf2+usMQ()p8P((H%$)t+Emxb32-CLQVCm-|bSEa+TSY3>a%`+1{e_!jm zx?LBJ%eSuWHXmfjRojMpnL7`h8&Q5&+Y(8JX7x}Yz#d8+G*SYDD2w3?Wg&b{nF}9N zdf^zQ8;(*s;9bg8IG`o(44|H-3*ane5Kbecq2YWuMOg%263QF|;0r4F;bg*70H0AQ zA3mkbgHI^u!AA*84xFG;Hhf6wgX5HQp)+C0f)A+Vfey+{c%RY*?FoxhhWDtJ0Y@m) z;V@+y97=fX@D7!1aFEgpQOXq9pRib None: ) self.theme_change_signal = Signal[Theme](self, "theme-changed") self.theme = self.settings.theme - self.watch_environment_files() + if self.settings.watch_env_files: + self.watch_environment_files() def get_default_screen(self) -> MainScreen: self.main_screen = MainScreen( diff --git a/src/posting/config.py b/src/posting/config.py index c889965e..7d3eeec0 100644 --- a/src/posting/config.py +++ b/src/posting/config.py @@ -125,6 +125,9 @@ class Settings(BaseSettings): using the `${VARIABLE_NAME}` syntax. When disabled, you are restricted to variables defined in any `.env` files explicitly supplied via the `--env` option.""" + watch_env_files: bool = Field(default=True) + """If enabled, automatically reload environment files when they change.""" + text_input: TextInputSettings = Field(default_factory=TextInputSettings) """General configuration for inputs and text area widgets.""" diff --git a/tests/sample-configs/custom_theme.yaml b/tests/sample-configs/custom_theme.yaml index 86fcb6cc..e23bb27a 100644 --- a/tests/sample-configs/custom_theme.yaml +++ b/tests/sample-configs/custom_theme.yaml @@ -8,3 +8,5 @@ heading: show_version: false text_input: blinking_cursor: false +watch_env_files: false + diff --git a/tests/sample-configs/custom_theme2.yaml b/tests/sample-configs/custom_theme2.yaml index a662b2de..47bb8658 100644 --- a/tests/sample-configs/custom_theme2.yaml +++ b/tests/sample-configs/custom_theme2.yaml @@ -8,3 +8,4 @@ heading: show_version: false text_input: blinking_cursor: false +watch_env_files: false \ No newline at end of file diff --git a/tests/sample-configs/general.yaml b/tests/sample-configs/general.yaml index 47da5e71..17049246 100644 --- a/tests/sample-configs/general.yaml +++ b/tests/sample-configs/general.yaml @@ -7,3 +7,4 @@ heading: show_version: false text_input: blinking_cursor: false +watch_env_files: false \ No newline at end of file diff --git a/tests/sample-configs/modified_config.yaml b/tests/sample-configs/modified_config.yaml index d0c09d3c..b80a810b 100644 --- a/tests/sample-configs/modified_config.yaml +++ b/tests/sample-configs/modified_config.yaml @@ -10,4 +10,5 @@ focus: heading: visible: false text_input: - blinking_cursor: false \ No newline at end of file + blinking_cursor: false +watch_env_files: false \ No newline at end of file From 08880a079986c86bb56455b58067b6700efe9cbe Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sun, 8 Sep 2024 22:00:01 +0100 Subject: [PATCH 7/9] Ensure variable inputs receive updated env --- src/posting/widgets/variable_autocomplete.py | 8 +++----- src/posting/widgets/variable_input.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/posting/widgets/variable_autocomplete.py b/src/posting/widgets/variable_autocomplete.py index 55593b13..168d0594 100644 --- a/src/posting/widgets/variable_autocomplete.py +++ b/src/posting/widgets/variable_autocomplete.py @@ -46,13 +46,11 @@ def __init__( classes, disabled, ) - self.variable_candidates = variable_candidates - - def on_mount(self) -> None: - if self.variable_candidates is None: - self.variable_candidates = [ + if variable_candidates is None: + variable_candidates = [ DropdownItem(main=f"${variable}") for variable in get_variables() ] + self.variable_candidates = variable_candidates def get_candidates(self, target_state: TargetState) -> list[DropdownItem]: cursor = target_state.selection.end[1] diff --git a/src/posting/widgets/variable_input.py b/src/posting/widgets/variable_input.py index cf24af89..250dd9a5 100644 --- a/src/posting/widgets/variable_input.py +++ b/src/posting/widgets/variable_input.py @@ -1,6 +1,10 @@ +from textual import on +from textual.widgets import Input +from textual_autocomplete import DropdownItem, TargetState from posting.help_screen import HelpData from posting.highlighters import VariableHighlighter from posting.themes import Theme +from posting.variables import get_variables from posting.widgets.input import PostingInput from posting.widgets.variable_autocomplete import VariableAutoComplete @@ -22,12 +26,11 @@ def on_mount(self) -> None: self.highlighter = VariableHighlighter() self.auto_complete = VariableAutoComplete( candidates=[], + variable_candidates=self._get_variable_candidates, target=self, ) self.screen.mount(self.auto_complete) - # Trigger the callback to set the initial highlighter - def on_theme_change(self, theme: Theme) -> None: """Callback which fires when the app-level theme changes in order to update the color scheme of the variable highlighter. @@ -39,3 +42,6 @@ def on_theme_change(self, theme: Theme) -> None: if theme.variable: self.highlighter.variable_styles = theme.variable.fill_with_defaults(theme) self.refresh() + + def _get_variable_candidates(self, target_state: TargetState) -> list[DropdownItem]: + return [DropdownItem(main=f"${variable}") for variable in get_variables()] From eaf0ecfcd1fb1a597814f4cdfdc613bf32085323 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sun, 8 Sep 2024 22:04:11 +0100 Subject: [PATCH 8/9] Remove useless async --- src/posting/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posting/app.py b/src/posting/app.py index 8130f9bf..470569af 100644 --- a/src/posting/app.py +++ b/src/posting/app.py @@ -147,7 +147,7 @@ def __init__( self.environment_files = environment_files self.settings = SETTINGS.get() - async def on_mount(self) -> None: + def on_mount(self) -> None: self.layout = self._initial_layout # Set the initial focus based on the settings. From 95ed49f79c9e0df1b5297c1a1bcc6986a381fa53 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sun, 8 Sep 2024 22:06:08 +0100 Subject: [PATCH 9/9] Update changelog --- docs/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 015b284e..4927a0e3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,7 @@ - Upgraded all dependencies - Remove `pydantic-settings` crash workaround on empty config files. - Renaming `App.maximized` as it now clashes with a Textual concept. +- Removed "using default collection" message from startup. ### Fixed