diff --git a/dev-requirements.txt b/dev-requirements.txt index f3b24c91..672ad32a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,3 @@ -filelock packaging pytest pytest-cov diff --git a/integration-tests/requirements.txt b/integration-tests/requirements.txt index 3ef31e4e..c086c7fc 100644 --- a/integration-tests/requirements.txt +++ b/integration-tests/requirements.txt @@ -1,4 +1,3 @@ -filelock pytest pytest-cov pytest-asyncio diff --git a/setup.py b/setup.py index 4c22da66..0bb5a1ce 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ "jinja2", "pluggy==1.*", "backoff", + "filelock", "requests", "bcrypt", "jupyterhub-traefik-proxy==1.*", diff --git a/tljh/config.py b/tljh/config.py index d72b03e5..3b317f93 100644 --- a/tljh/config.py +++ b/tljh/config.py @@ -18,9 +18,11 @@ import sys import time from collections.abc import Mapping, Sequence +from contextlib import contextmanager from copy import deepcopy import requests +from filelock import FileLock, Timeout from .yaml import yaml @@ -32,6 +34,22 @@ CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yaml") +@contextmanager +def config_file_lock(config_path, timeout=1): + """Context manager to acquire the config file lock""" + lock_file = f"{config_path}.lock" + try: + with FileLock(lock_file).acquire(timeout=timeout): + yield + + except Timeout: + print( + f"Another instance of tljh-config holds the lock {lock_file}.", + file=sys.stderr, + ) + sys.exit(1) + + def set_item_in_config(config, property_path, value): """ Set key at property_path to value in config & return new config. @@ -169,9 +187,10 @@ def validate_config(config, validate): print( f"Config validation error: {e.message}.\n" "You can still apply this change without validation by re-running your command with the --no-validate flag.\n" - "If you think this validation error is incorrect, please report it to https://github.com/jupyterhub/the-littlest-jupyterhub/issues." + "If you think this validation error is incorrect, please report it to https://github.com/jupyterhub/the-littlest-jupyterhub/issues.", + file=sys.stderr, ) - exit() + sys.exit(1) def show_config(config_path): @@ -186,88 +205,52 @@ def set_config_value(config_path, key_path, value, validate=True): """ Set key at key_path in config_path to value """ - from filelock import FileLock, Timeout + with config_file_lock(config_path): + config = get_current_config(config_path) + config = set_item_in_config(config, key_path, value) + validate_config(config, validate) - lock_file = f"{config_path}.lock" - lock = FileLock(lock_file) - try: - with lock.acquire(timeout=1): - config = get_current_config(config_path) - config = set_item_in_config(config, key_path, value) - validate_config(config, validate) - - with open(config_path, "w") as f: - yaml.dump(config, f) - - except Timeout: - print(f"Another instance of tljh-config holds the lock {lock_file}") - exit(1) + with open(config_path, "w") as f: + yaml.dump(config, f) def unset_config_value(config_path, key_path, validate=True): """ Unset key at key_path in config_path """ - from filelock import FileLock, Timeout + with config_file_lock(config_path): + config = get_current_config(config_path) + config = unset_item_from_config(config, key_path) + validate_config(config, validate) - lock_file = f"{config_path}.lock" - lock = FileLock(lock_file) - try: - with lock.acquire(timeout=1): - config = get_current_config(config_path) - config = unset_item_from_config(config, key_path) - validate_config(config, validate) - - with open(config_path, "w") as f: - yaml.dump(config, f) - - except Timeout: - print(f"Another instance of tljh-config holds the lock {lock_file}") - exit(1) + with open(config_path, "w") as f: + yaml.dump(config, f) def add_config_value(config_path, key_path, value, validate=True): """ Add value to list at key_path """ - from filelock import FileLock, Timeout + with config_file_lock(config_path): + config = get_current_config(config_path) + config = add_item_to_config(config, key_path, value) + validate_config(config, validate) - lock_file = f"{config_path}.lock" - lock = FileLock(lock_file) - try: - with lock.acquire(timeout=1): - config = get_current_config(config_path) - config = add_item_to_config(config, key_path, value) - validate_config(config, validate) - - with open(config_path, "w") as f: - yaml.dump(config, f) - - except Timeout: - print(f"Another instance of tljh-config holds the lock {lock_file}") - exit(1) + with open(config_path, "w") as f: + yaml.dump(config, f) def remove_config_value(config_path, key_path, value, validate=True): """ Remove value from list at key_path """ - from filelock import FileLock, Timeout + with config_file_lock(config_path): + config = get_current_config(config_path) + config = remove_item_from_config(config, key_path, value) + validate_config(config, validate) - lock_file = f"{config_path}.lock" - lock = FileLock(lock_file) - try: - with lock.acquire(timeout=1): - config = get_current_config(config_path) - config = remove_item_from_config(config, key_path, value) - validate_config(config, validate) - - with open(config_path, "w") as f: - yaml.dump(config, f) - - except Timeout: - print(f"Another instance of tljh-config holds the lock {lock_file}") - exit(1) + with open(config_path, "w") as f: + yaml.dump(config, f) def get_current_config(config_path): diff --git a/tljh/requirements-hub-env.txt b/tljh/requirements-hub-env.txt index 25b29deb..0c455723 100644 --- a/tljh/requirements-hub-env.txt +++ b/tljh/requirements-hub-env.txt @@ -26,6 +26,3 @@ jupyterhub-idle-culler>=1.2.1,<2 # ref: https://github.com/jupyterhub/the-littlest-jupyterhub/issues/289 # pycurl>=7.45.2,<8 - -# filelock is used to help us do atomic operations on config file(s) -filelock>=3.15.4,<4