From ba0c9d22f821128b34f19e74b3412c1294e0df06 Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Tue, 6 Feb 2024 15:28:22 -0800 Subject: [PATCH 01/10] Better server config handling --- godata/cli/server.py | 7 +- godata/client/client.py | 7 +- godata/server/__init__.py | 152 ++++++++++++++++++----------- poetry.lock | 200 +++++++++++++++++--------------------- pyproject.toml | 1 + 5 files changed, 186 insertions(+), 181 deletions(-) diff --git a/godata/cli/server.py b/godata/cli/server.py index aadb7f9..6f2cca4 100644 --- a/godata/cli/server.py +++ b/godata/cli/server.py @@ -52,11 +52,10 @@ def stop(): "-p", help="Path to install the server binary.", type=click.Path(file_okay=False, resolve_path=True, path_type=Path), - default=srv.get_server_location(), ) -def install(upgrade: bool, path: Path): - print(f"Installing server at {path}") - srv.set_server_location(path) +def install(upgrade: bool, path: Path = None): + if path is not None: + srv.set_server_location(path) if upgrade: srv.upgrade() else: diff --git a/godata/client/client.py b/godata/client/client.py index 4fd62a0..a6b38c1 100644 --- a/godata/client/client.py +++ b/godata/client/client.py @@ -54,11 +54,8 @@ def check_server(client, url): @cache def get_client(): - try: - with open(Path.home() / ".godata_server", "r") as f: - SERVER_URL = f.read().strip() - except FileNotFoundError: - SERVER_URL = None + server_config = server.get_config() + SERVER_URL = server_config.server_url if not SERVER_URL: server.start() diff --git a/godata/server/__init__.py b/godata/server/__init__.py index 8746a12..4ebed1a 100644 --- a/godata/server/__init__.py +++ b/godata/server/__init__.py @@ -1,90 +1,124 @@ +import json import os -import pickle import signal import subprocess import time -from functools import cache from pathlib import Path +from typing import Optional from urllib import parse import appdirs +import portalocker +import pydantic from .install import DEFAULT_SERVER_INSTALL_LOCATION, install, upgrade -@cache -def get_server_path(): - config_path = Path(appdirs.user_config_dir("godata")) / "server_path" - if not config_path.exists(): - return DEFAULT_SERVER_INSTALL_LOCATION / "godata_server" +class ServerConfig(pydantic.BaseModel): + """ + Configuration specification for the godata server + """ - with open(config_path, "rb") as f: - return pickle.load(f) + is_running: bool = False + server_url: Optional[str] = None + server_path: Path = DEFAULT_SERVER_INSTALL_LOCATION / "godata_server" + port: Optional[int] = pydantic.Field(ge=0, le=65535, default=None) + def stop(self): + self.is_running = False + self.server_url = None + self.port = None -@cache -def get_server_location(): - full_path = get_server_path() - return full_path.parent + +# Create the default server config file if it does not exist +SERVER_CONFIG_PATH = Path(appdirs.user_config_dir("godata")) / "godata_server.json" +if not SERVER_CONFIG_PATH.parent.exists(): + SERVER_CONFIG_PATH.parent.mkdir(parents=True) + +if not SERVER_CONFIG_PATH.exists(): + with portalocker.Lock(SERVER_CONFIG_PATH, "w") as f: + json_data = ServerConfig().model_dump_json(indent=2) + f.write(json_data) + + +def get_config(): + with portalocker.Lock(SERVER_CONFIG_PATH, "r") as f: + config = json.load(f) + config = ServerConfig(**config) + return config def set_server_location(path: Path): - server_path = path / "godata_server" - config_path = Path(appdirs.user_config_dir("godata")) - config_path.mkdir(parents=True, exist_ok=True) - path_path = config_path / "server_path" - with open(path_path, "wb") as f: - pickle.dump(server_path, f) + if not path.is_dir(): + raise ValueError(f"{path} is not a valid directory.") + binary_path = path / "godata_server" + + with portalocker.Lock(SERVER_CONFIG_PATH, "r+") as f: + config = json.load(f) + config = ServerConfig(**config) + if config.is_running: + raise RuntimeError("Cannot change server path while server is running") + config.server_path = binary_path + # clear the file contents + f.seek(0) + f.truncate() + f.write(config.model_dump_json(indent=2)) def start(port: int = None): # check if a godata_server process is already running + with portalocker.Lock(SERVER_CONFIG_PATH, "r+") as f: + config = json.load(f) + config = ServerConfig(**config) + already_running = config.is_running + + if not already_running: + config.is_running = True + config.port = port + + try: + command = str(config.server_path) + if port: + command += f" --port={port}" + url = f"http://localhost:{port}" + else: + SERVER_PATH = str(Path.home() / ".godata.sock") + url = f"http+unix://{parse.quote(SERVER_PATH, safe='')}" + + config.server_url = url + subprocess.Popen(command, close_fds=True, shell=True) + except FileNotFoundError: + raise FileNotFoundError( + "Unable to start godata server: could not find the server binary. " + "Please run `godata server install` first." + ) + f.seek(0) + f.truncate() + f.write(config.model_dump_json(indent=2)) + time.sleep(0.1) - try: - server_pid = subprocess.check_output(["pgrep", "godata_server"]) - print( - f"Server is already running with PID {int(server_pid)}. " - "Please stop the server before starting a new one." - ) - return - except subprocess.CalledProcessError: - pass - - try: - command = str(get_server_path()) - if port: - command += f" --port={port}" - url = f"http://localhost:{port}" - else: - SERVER_PATH = str(Path.home() / ".godata.sock") - url = f"http+unix://{parse.quote(SERVER_PATH, safe='')}" - FILE_OUTPUT_PATH = Path.home() / ".godata_server" - with open(FILE_OUTPUT_PATH, "w") as f: - f.write(url) - subprocess.Popen(command, close_fds=True, shell=True) - except FileNotFoundError: - raise FileNotFoundError( - "Unable to start godata server: could not find the server binary. " - "Please run `godata server install` first." - ) - time.sleep(0.5) return True def stop(): - try: + with portalocker.Lock(SERVER_CONFIG_PATH, "r+") as f: + config = json.load(f) + config = ServerConfig(**config) + if not config.is_running: + raise RuntimeError("Server is not running.") + + # check if the url is a unix socket or localhost + local_urls = ["http://localhost", "http+unix://"] + if not any(config.server_url.startswith(url) for url in local_urls): + raise RuntimeError("Cannot stop server running on a remote host.") + server_pid = subprocess.check_output(["pgrep", "godata_server"]) - except subprocess.CalledProcessError: - print("Server is not running.") - return - # kill the server - print(int(server_pid)) - os.kill(int(server_pid), signal.SIGINT) - # remove the file that stores the server url - print("STOPPED") - FILE_OUTPUT_PATH = Path.home() / ".godata_server" - if FILE_OUTPUT_PATH.exists(): - FILE_OUTPUT_PATH.unlink() + os.kill(int(server_pid), signal.SIGINT) + + config.stop() + f.seek(0) + f.truncate() + f.write(config.model_dump_json(indent=2)) __all__ = ["start", "stop", "install", "upgrade"] diff --git a/poetry.lock b/poetry.lock index 8ebf19c..be8d1e4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1019,18 +1019,18 @@ numpy = ">=1.16.6" [[package]] name = "pydantic" -version = "2.5.2" +version = "2.6.1" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-2.5.2-py3-none-any.whl", hash = "sha256:80c50fb8e3dcecfddae1adbcc00ec5822918490c99ab31f6cf6140ca1c1429f0"}, - {file = "pydantic-2.5.2.tar.gz", hash = "sha256:ff177ba64c6faf73d7afa2e8cad38fd456c0dbe01c9954e71038001cd15a6edd"}, + {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, + {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.14.5" +pydantic-core = "2.16.2" typing-extensions = ">=4.6.1" [package.extras] @@ -1038,116 +1038,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.14.5" +version = "2.16.2" description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.14.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:7e88f5696153dc516ba6e79f82cc4747e87027205f0e02390c21f7cb3bd8abfd"}, - {file = "pydantic_core-2.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4641e8ad4efb697f38a9b64ca0523b557c7931c5f84e0fd377a9a3b05121f0de"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774de879d212db5ce02dfbf5b0da9a0ea386aeba12b0b95674a4ce0593df3d07"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebb4e035e28f49b6f1a7032920bb9a0c064aedbbabe52c543343d39341a5b2a3"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b53e9ad053cd064f7e473a5f29b37fc4cc9dc6d35f341e6afc0155ea257fc911"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aa1768c151cf562a9992462239dfc356b3d1037cc5a3ac829bb7f3bda7cc1f9"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac5c82fc632c599f4639a5886f96867ffced74458c7db61bc9a66ccb8ee3113"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae91f50ccc5810b2f1b6b858257c9ad2e08da70bf890dee02de1775a387c66"}, - {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6b9ff467ffbab9110e80e8c8de3bcfce8e8b0fd5661ac44a09ae5901668ba997"}, - {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61ea96a78378e3bd5a0be99b0e5ed00057b71f66115f5404d0dae4819f495093"}, - {file = "pydantic_core-2.14.5-cp310-none-win32.whl", hash = "sha256:bb4c2eda937a5e74c38a41b33d8c77220380a388d689bcdb9b187cf6224c9720"}, - {file = "pydantic_core-2.14.5-cp310-none-win_amd64.whl", hash = "sha256:b7851992faf25eac90bfcb7bfd19e1f5ffa00afd57daec8a0042e63c74a4551b"}, - {file = "pydantic_core-2.14.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4e40f2bd0d57dac3feb3a3aed50f17d83436c9e6b09b16af271b6230a2915459"}, - {file = "pydantic_core-2.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab1cdb0f14dc161ebc268c09db04d2c9e6f70027f3b42446fa11c153521c0e88"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aae7ea3a1c5bb40c93cad361b3e869b180ac174656120c42b9fadebf685d121b"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60b7607753ba62cf0739177913b858140f11b8af72f22860c28eabb2f0a61937"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2248485b0322c75aee7565d95ad0e16f1c67403a470d02f94da7344184be770f"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:823fcc638f67035137a5cd3f1584a4542d35a951c3cc68c6ead1df7dac825c26"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96581cfefa9123accc465a5fd0cc833ac4d75d55cc30b633b402e00e7ced00a6"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a33324437018bf6ba1bb0f921788788641439e0ed654b233285b9c69704c27b4"}, - {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9bd18fee0923ca10f9a3ff67d4851c9d3e22b7bc63d1eddc12f439f436f2aada"}, - {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:853a2295c00f1d4429db4c0fb9475958543ee80cfd310814b5c0ef502de24dda"}, - {file = "pydantic_core-2.14.5-cp311-none-win32.whl", hash = "sha256:cb774298da62aea5c80a89bd58c40205ab4c2abf4834453b5de207d59d2e1651"}, - {file = "pydantic_core-2.14.5-cp311-none-win_amd64.whl", hash = "sha256:e87fc540c6cac7f29ede02e0f989d4233f88ad439c5cdee56f693cc9c1c78077"}, - {file = "pydantic_core-2.14.5-cp311-none-win_arm64.whl", hash = "sha256:57d52fa717ff445cb0a5ab5237db502e6be50809b43a596fb569630c665abddf"}, - {file = "pydantic_core-2.14.5-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:e60f112ac88db9261ad3a52032ea46388378034f3279c643499edb982536a093"}, - {file = "pydantic_core-2.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e227c40c02fd873c2a73a98c1280c10315cbebe26734c196ef4514776120aeb"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0cbc7fff06a90bbd875cc201f94ef0ee3929dfbd5c55a06674b60857b8b85ed"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:103ef8d5b58596a731b690112819501ba1db7a36f4ee99f7892c40da02c3e189"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c949f04ecad823f81b1ba94e7d189d9dfb81edbb94ed3f8acfce41e682e48cef"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1452a1acdf914d194159439eb21e56b89aa903f2e1c65c60b9d874f9b950e5d"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4679d4c2b089e5ef89756bc73e1926745e995d76e11925e3e96a76d5fa51fc"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf9d3fe53b1ee360e2421be95e62ca9b3296bf3f2fb2d3b83ca49ad3f925835e"}, - {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70f4b4851dbb500129681d04cc955be2a90b2248d69273a787dda120d5cf1f69"}, - {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:59986de5710ad9613ff61dd9b02bdd2f615f1a7052304b79cc8fa2eb4e336d2d"}, - {file = "pydantic_core-2.14.5-cp312-none-win32.whl", hash = "sha256:699156034181e2ce106c89ddb4b6504c30db8caa86e0c30de47b3e0654543260"}, - {file = "pydantic_core-2.14.5-cp312-none-win_amd64.whl", hash = "sha256:5baab5455c7a538ac7e8bf1feec4278a66436197592a9bed538160a2e7d11e36"}, - {file = "pydantic_core-2.14.5-cp312-none-win_arm64.whl", hash = "sha256:e47e9a08bcc04d20975b6434cc50bf82665fbc751bcce739d04a3120428f3e27"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:af36f36538418f3806048f3b242a1777e2540ff9efaa667c27da63d2749dbce0"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:45e95333b8418ded64745f14574aa9bfc212cb4fbeed7a687b0c6e53b5e188cd"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e47a76848f92529879ecfc417ff88a2806438f57be4a6a8bf2961e8f9ca9ec7"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d81e6987b27bc7d101c8597e1cd2bcaa2fee5e8e0f356735c7ed34368c471550"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34708cc82c330e303f4ce87758828ef6e457681b58ce0e921b6e97937dd1e2a3"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c1988019752138b974c28f43751528116bcceadad85f33a258869e641d753"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e4d090e73e0725b2904fdbdd8d73b8802ddd691ef9254577b708d413bf3006e"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c7d5b5005f177764e96bd584d7bf28d6e26e96f2a541fdddb934c486e36fd59"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a71891847f0a73b1b9eb86d089baee301477abef45f7eaf303495cd1473613e4"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a717aef6971208f0851a2420b075338e33083111d92041157bbe0e2713b37325"}, - {file = "pydantic_core-2.14.5-cp37-none-win32.whl", hash = "sha256:de790a3b5aa2124b8b78ae5faa033937a72da8efe74b9231698b5a1dd9be3405"}, - {file = "pydantic_core-2.14.5-cp37-none-win_amd64.whl", hash = "sha256:6c327e9cd849b564b234da821236e6bcbe4f359a42ee05050dc79d8ed2a91588"}, - {file = "pydantic_core-2.14.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:ef98ca7d5995a82f43ec0ab39c4caf6a9b994cb0b53648ff61716370eadc43cf"}, - {file = "pydantic_core-2.14.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6eae413494a1c3f89055da7a5515f32e05ebc1a234c27674a6956755fb2236f"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcf4e6d85614f7a4956c2de5a56531f44efb973d2fe4a444d7251df5d5c4dcfd"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6637560562134b0e17de333d18e69e312e0458ee4455bdad12c37100b7cad706"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77fa384d8e118b3077cccfcaf91bf83c31fe4dc850b5e6ee3dc14dc3d61bdba1"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16e29bad40bcf97aac682a58861249ca9dcc57c3f6be22f506501833ddb8939c"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531f4b4252fac6ca476fbe0e6f60f16f5b65d3e6b583bc4d87645e4e5ddde331"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:074f3d86f081ce61414d2dc44901f4f83617329c6f3ab49d2bc6c96948b2c26b"}, - {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2adbe22ab4babbca99c75c5d07aaf74f43c3195384ec07ccbd2f9e3bddaecec"}, - {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0f6116a558fd06d1b7c2902d1c4cf64a5bd49d67c3540e61eccca93f41418124"}, - {file = "pydantic_core-2.14.5-cp38-none-win32.whl", hash = "sha256:fe0a5a1025eb797752136ac8b4fa21aa891e3d74fd340f864ff982d649691867"}, - {file = "pydantic_core-2.14.5-cp38-none-win_amd64.whl", hash = "sha256:079206491c435b60778cf2b0ee5fd645e61ffd6e70c47806c9ed51fc75af078d"}, - {file = "pydantic_core-2.14.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:a6a16f4a527aae4f49c875da3cdc9508ac7eef26e7977952608610104244e1b7"}, - {file = "pydantic_core-2.14.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abf058be9517dc877227ec3223f0300034bd0e9f53aebd63cf4456c8cb1e0863"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49b08aae5013640a3bfa25a8eebbd95638ec3f4b2eaf6ed82cf0c7047133f03b"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2d97e906b4ff36eb464d52a3bc7d720bd6261f64bc4bcdbcd2c557c02081ed2"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3128e0bbc8c091ec4375a1828d6118bc20404883169ac95ffa8d983b293611e6"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88e74ab0cdd84ad0614e2750f903bb0d610cc8af2cc17f72c28163acfcf372a4"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c339dabd8ee15f8259ee0f202679b6324926e5bc9e9a40bf981ce77c038553db"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3387277f1bf659caf1724e1afe8ee7dbc9952a82d90f858ebb931880216ea955"}, - {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ba6b6b3846cfc10fdb4c971980a954e49d447cd215ed5a77ec8190bc93dd7bc5"}, - {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca61d858e4107ce5e1330a74724fe757fc7135190eb5ce5c9d0191729f033209"}, - {file = "pydantic_core-2.14.5-cp39-none-win32.whl", hash = "sha256:ec1e72d6412f7126eb7b2e3bfca42b15e6e389e1bc88ea0069d0cc1742f477c6"}, - {file = "pydantic_core-2.14.5-cp39-none-win_amd64.whl", hash = "sha256:c0b97ec434041827935044bbbe52b03d6018c2897349670ff8fe11ed24d1d4ab"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79e0a2cdbdc7af3f4aee3210b1172ab53d7ddb6a2d8c24119b5706e622b346d0"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:678265f7b14e138d9a541ddabbe033012a2953315739f8cfa6d754cc8063e8ca"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b15e855ae44f0c6341ceb74df61b606e11f1087e87dcb7482377374aac6abe"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b0e985fbaf13e6b06a56d21694d12ebca6ce5414b9211edf6f17738d82b0f8"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ad873900297bb36e4b6b3f7029d88ff9829ecdc15d5cf20161775ce12306f8a"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2d0ae0d8670164e10accbeb31d5ad45adb71292032d0fdb9079912907f0085f4"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d37f8ec982ead9ba0a22a996129594938138a1503237b87318392a48882d50b7"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:35613015f0ba7e14c29ac6c2483a657ec740e5ac5758d993fdd5870b07a61d8b"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab4ea451082e684198636565224bbb179575efc1658c48281b2c866bfd4ddf04"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ce601907e99ea5b4adb807ded3570ea62186b17f88e271569144e8cca4409c7"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb2ed8b3fe4bf4506d6dab3b93b83bbc22237e230cba03866d561c3577517d18"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70f947628e074bb2526ba1b151cee10e4c3b9670af4dbb4d73bc8a89445916b5"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4bc536201426451f06f044dfbf341c09f540b4ebdb9fd8d2c6164d733de5e634"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4791cf0f8c3104ac668797d8c514afb3431bc3305f5638add0ba1a5a37e0d88"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:038c9f763e650712b899f983076ce783175397c848da04985658e7628cbe873b"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:27548e16c79702f1e03f5628589c6057c9ae17c95b4c449de3c66b589ead0520"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97bee68898f3f4344eb02fec316db93d9700fb1e6a5b760ffa20d71d9a46ce3"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b759b77f5337b4ea024f03abc6464c9f35d9718de01cfe6bae9f2e139c397e"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:439c9afe34638ace43a49bf72d201e0ffc1a800295bed8420c2a9ca8d5e3dbb3"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ba39688799094c75ea8a16a6b544eb57b5b0f3328697084f3f2790892510d144"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ccd4d5702bb90b84df13bd491be8d900b92016c5a455b7e14630ad7449eb03f8"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:81982d78a45d1e5396819bbb4ece1fadfe5f079335dd28c4ab3427cd95389944"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f8210297b04e53bc3da35db08b7302a6a1f4889c79173af69b72ec9754796b8"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8c8a8812fe6f43a3a5b054af6ac2d7b8605c7bcab2804a8a7d68b53f3cd86e00"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:206ed23aecd67c71daf5c02c3cd19c0501b01ef3cbf7782db9e4e051426b3d0d"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2027d05c8aebe61d898d4cffd774840a9cb82ed356ba47a90d99ad768f39789"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40180930807ce806aa71eda5a5a5447abb6b6a3c0b4b3b1b1962651906484d68"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:615a0a4bff11c45eb3c1996ceed5bdaa2f7b432425253a7c2eed33bb86d80abc"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5e412d717366e0677ef767eac93566582518fe8be923361a5c204c1a62eaafe"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:513b07e99c0a267b1d954243845d8a833758a6726a3b5d8948306e3fe14675e3"}, - {file = "pydantic_core-2.14.5.tar.gz", hash = "sha256:6d30226dfc816dd0fdf120cae611dd2215117e4f9b124af8c60ab9093b6e8e71"}, + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, + {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, + {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, + {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, + {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, + {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, + {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, + {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, + {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, + {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, + {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, + {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, + {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, + {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, ] [package.dependencies] @@ -1605,4 +1579,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "f5173ece60a727a130522b1d368f68117b5f051fadfa8e91c07618cbc962c7e8" +content-hash = "995be61be62200ebb23c8ccf68fb5e51197436291a645a06812e887c5b7d645e" diff --git a/pyproject.toml b/pyproject.toml index 60334d3..b5763c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ click = "^8.1.7" portalocker = "^2.8.2" appdirs = "^1.4.4" packaging = "^23.2" +pydantic = "^2.6.1" [tool.poetry.scripts] godata = "godata.cli.cli:main" From 5a7ac363928112e51bbfee9bc796bce736b2aaec Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Tue, 6 Feb 2024 15:37:39 -0800 Subject: [PATCH 02/10] breakout into different files --- godata/server/__init__.py | 127 ++------------------------------------ godata/server/cmd.py | 84 +++++++++++++++++++++++++ godata/server/config.py | 47 ++++++++++++++ 3 files changed, 135 insertions(+), 123 deletions(-) create mode 100644 godata/server/cmd.py create mode 100644 godata/server/config.py diff --git a/godata/server/__init__.py b/godata/server/__init__.py index 4ebed1a..32650e5 100644 --- a/godata/server/__init__.py +++ b/godata/server/__init__.py @@ -1,124 +1,5 @@ -import json -import os -import signal -import subprocess -import time -from pathlib import Path -from typing import Optional -from urllib import parse +from .cmd import set_server_location, start, stop +from .config import get_config +from .install import install, upgrade -import appdirs -import portalocker -import pydantic - -from .install import DEFAULT_SERVER_INSTALL_LOCATION, install, upgrade - - -class ServerConfig(pydantic.BaseModel): - """ - Configuration specification for the godata server - """ - - is_running: bool = False - server_url: Optional[str] = None - server_path: Path = DEFAULT_SERVER_INSTALL_LOCATION / "godata_server" - port: Optional[int] = pydantic.Field(ge=0, le=65535, default=None) - - def stop(self): - self.is_running = False - self.server_url = None - self.port = None - - -# Create the default server config file if it does not exist -SERVER_CONFIG_PATH = Path(appdirs.user_config_dir("godata")) / "godata_server.json" -if not SERVER_CONFIG_PATH.parent.exists(): - SERVER_CONFIG_PATH.parent.mkdir(parents=True) - -if not SERVER_CONFIG_PATH.exists(): - with portalocker.Lock(SERVER_CONFIG_PATH, "w") as f: - json_data = ServerConfig().model_dump_json(indent=2) - f.write(json_data) - - -def get_config(): - with portalocker.Lock(SERVER_CONFIG_PATH, "r") as f: - config = json.load(f) - config = ServerConfig(**config) - return config - - -def set_server_location(path: Path): - if not path.is_dir(): - raise ValueError(f"{path} is not a valid directory.") - binary_path = path / "godata_server" - - with portalocker.Lock(SERVER_CONFIG_PATH, "r+") as f: - config = json.load(f) - config = ServerConfig(**config) - if config.is_running: - raise RuntimeError("Cannot change server path while server is running") - config.server_path = binary_path - # clear the file contents - f.seek(0) - f.truncate() - f.write(config.model_dump_json(indent=2)) - - -def start(port: int = None): - # check if a godata_server process is already running - with portalocker.Lock(SERVER_CONFIG_PATH, "r+") as f: - config = json.load(f) - config = ServerConfig(**config) - already_running = config.is_running - - if not already_running: - config.is_running = True - config.port = port - - try: - command = str(config.server_path) - if port: - command += f" --port={port}" - url = f"http://localhost:{port}" - else: - SERVER_PATH = str(Path.home() / ".godata.sock") - url = f"http+unix://{parse.quote(SERVER_PATH, safe='')}" - - config.server_url = url - subprocess.Popen(command, close_fds=True, shell=True) - except FileNotFoundError: - raise FileNotFoundError( - "Unable to start godata server: could not find the server binary. " - "Please run `godata server install` first." - ) - f.seek(0) - f.truncate() - f.write(config.model_dump_json(indent=2)) - time.sleep(0.1) - - return True - - -def stop(): - with portalocker.Lock(SERVER_CONFIG_PATH, "r+") as f: - config = json.load(f) - config = ServerConfig(**config) - if not config.is_running: - raise RuntimeError("Server is not running.") - - # check if the url is a unix socket or localhost - local_urls = ["http://localhost", "http+unix://"] - if not any(config.server_url.startswith(url) for url in local_urls): - raise RuntimeError("Cannot stop server running on a remote host.") - - server_pid = subprocess.check_output(["pgrep", "godata_server"]) - os.kill(int(server_pid), signal.SIGINT) - - config.stop() - f.seek(0) - f.truncate() - f.write(config.model_dump_json(indent=2)) - - -__all__ = ["start", "stop", "install", "upgrade"] +__all__ = ["start", "stop", "install", "upgrade", "get_config", "set_server_location"] diff --git a/godata/server/cmd.py b/godata/server/cmd.py new file mode 100644 index 0000000..f6a5760 --- /dev/null +++ b/godata/server/cmd.py @@ -0,0 +1,84 @@ +import json +import os +import signal +import subprocess +import time +from pathlib import Path +from urllib import parse + +import portalocker + +from .config import SERVER_CONFIG_PATH, ServerConfig + + +def set_server_location(path: Path): + if not path.is_dir(): + raise ValueError(f"{path} is not a valid directory.") + binary_path = path / "godata_server" + + with portalocker.Lock(SERVER_CONFIG_PATH, "r+") as f: + config = json.load(f) + config = ServerConfig(**config) + if config.is_running: + raise RuntimeError("Cannot change server path while server is running") + config.server_path = binary_path + # clear the file contents + f.seek(0) + f.truncate() + f.write(config.model_dump_json(indent=2)) + + +def start(port: int = None): + # check if a godata_server process is already running + with portalocker.Lock(SERVER_CONFIG_PATH, "r+") as f: + config = json.load(f) + config = ServerConfig(**config) + already_running = config.is_running + + if not already_running: + config.is_running = True + config.port = port + + try: + command = str(config.server_path) + if port: + command += f" --port={port}" + url = f"http://localhost:{port}" + else: + SERVER_PATH = str(Path.home() / ".godata.sock") + url = f"http+unix://{parse.quote(SERVER_PATH, safe='')}" + + config.server_url = url + subprocess.Popen(command, close_fds=True, shell=True) + except FileNotFoundError: + raise FileNotFoundError( + "Unable to start godata server: could not find the server binary. " + "Please run `godata server install` first." + ) + f.seek(0) + f.truncate() + f.write(config.model_dump_json(indent=2)) + time.sleep(0.1) + + return True + + +def stop(): + with portalocker.Lock(SERVER_CONFIG_PATH, "r+") as f: + config = json.load(f) + config = ServerConfig(**config) + if not config.is_running: + raise RuntimeError("Server is not running.") + + # check if the url is a unix socket or localhost + local_urls = ["http://localhost", "http+unix://"] + if not any(config.server_url.startswith(url) for url in local_urls): + raise RuntimeError("Cannot stop server running on a remote host.") + + server_pid = subprocess.check_output(["pgrep", "godata_server"]) + os.kill(int(server_pid), signal.SIGINT) + + config.stop() + f.seek(0) + f.truncate() + f.write(config.model_dump_json(indent=2)) diff --git a/godata/server/config.py b/godata/server/config.py new file mode 100644 index 0000000..3582dae --- /dev/null +++ b/godata/server/config.py @@ -0,0 +1,47 @@ +import json +from pathlib import Path +from typing import Optional + +import appdirs +import portalocker +import pydantic + +from .install import DEFAULT_SERVER_INSTALL_LOCATION + +SERVER_CONFIG_PATH = Path(appdirs.user_config_dir("godata")) / "godata_server.json" + + +class ServerConfig(pydantic.BaseModel): + """ + Configuration specification for the godata server + """ + + is_running: bool = False + server_url: Optional[str] = None + server_path: Path = DEFAULT_SERVER_INSTALL_LOCATION / "godata_server" + port: Optional[int] = pydantic.Field(ge=0, le=65535, default=None) + + def stop(self): + self.is_running = False + self.server_url = None + self.port = None + + +# Create the default server config file if it does not exist +def create_default_config(): + if not SERVER_CONFIG_PATH.parent.exists(): + SERVER_CONFIG_PATH.parent.mkdir(parents=True) + if not SERVER_CONFIG_PATH.exists(): + with portalocker.Lock(SERVER_CONFIG_PATH, "w") as f: + json_data = ServerConfig().model_dump_json(indent=2) + f.write(json_data) + + +def get_config(): + with portalocker.Lock(SERVER_CONFIG_PATH, "r") as f: + config = json.load(f) + config = ServerConfig(**config) + return config + + +create_default_config() From bea0bbd7a2ea29e3f05176a42eb9c9511fdbc3a4 Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Tue, 6 Feb 2024 16:00:39 -0800 Subject: [PATCH 03/10] Add testing for multithreaded server starting --- godata/client/client.py | 3 +++ godata/server/cmd.py | 2 +- tests/test_server.py | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/test_server.py diff --git a/godata/client/client.py b/godata/client/client.py index a6b38c1..778b9b9 100644 --- a/godata/client/client.py +++ b/godata/client/client.py @@ -166,6 +166,9 @@ def drop_project(collection_name: str, project_name: str): return {} if resp.status_code == 200: return resp.json() + elif resp.status_code == 404: + # Project is not loaded, so we don't care + return {} else: raise GodataClientError(f"{resp.status_code}: {resp.text}") diff --git a/godata/server/cmd.py b/godata/server/cmd.py index f6a5760..0ac73d4 100644 --- a/godata/server/cmd.py +++ b/godata/server/cmd.py @@ -60,7 +60,7 @@ def start(port: int = None): f.write(config.model_dump_json(indent=2)) time.sleep(0.1) - return True + return not already_running def stop(): diff --git a/tests/test_server.py b/tests/test_server.py new file mode 100644 index 0000000..e89ff56 --- /dev/null +++ b/tests/test_server.py @@ -0,0 +1,26 @@ +import multiprocessing as mp + + +def setup_module(module): + # Make sure the server is running + from godata.server import stop + + try: + stop() + except RuntimeError: + # If the server is already not running, that's fine. + + pass + + +def test_many_start(): + from godata.server import start + + # max number of processes available + n = mp.cpu_count() + if n < 2: + raise RuntimeError("This test requires at least 2 CPUs") + # run the command in n threads, and collect the return values + with mp.Pool(n) as pool: + results = pool.map(start, range(n)) + assert results[0] and not any(results[1:]) From 2018062d78bc626f79a828725349a60092437909 Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Wed, 7 Feb 2024 16:04:19 -0800 Subject: [PATCH 04/10] Fix server start testing --- tests/test_server.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_server.py b/tests/test_server.py index e89ff56..2edfc66 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -23,4 +23,6 @@ def test_many_start(): # run the command in n threads, and collect the return values with mp.Pool(n) as pool: results = pool.map(start, range(n)) - assert results[0] and not any(results[1:]) + + # We should get one True and n-1 False + assert results.count(True) == 1 From 67c854922cbd850070690e99b350f6ffb7b91e36 Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Wed, 7 Feb 2024 16:15:04 -0800 Subject: [PATCH 05/10] Update CI for deprecation warnings --- .github/workflows/ci.yml | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10cb604..8c92aec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,12 +29,11 @@ jobs: matrix: target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu] steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: toolchain: stable - target: ${{ matrix.target }} - override: true + targets: ${{ matrix.target }} - uses: actions-rs/cargo@v1 with: use-cross: true @@ -46,6 +45,7 @@ jobs: with: name: godata_server-${{ matrix.target }} path: godata_server-${{ matrix.target }}.zip + mac_build_server: name: Build server on macOS runs-on: macos-latest @@ -54,18 +54,12 @@ jobs: matrix: target: [x86_64-apple-darwin, aarch64-apple-darwin] steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: toolchain: stable - target: ${{ matrix.target }} - override: true - - id: build - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target ${{ matrix.target }} + targets: ${{ matrix.target }} + - run: cargo build --release --target ${{ matrix.target }} - name: zip artifact run: zip -j godata_server-${{ matrix.target }}.zip target/${{ matrix.target }}/release/godata_server @@ -85,7 +79,7 @@ jobs: target: [x86_64-unknown-linux-gnu] python-version: ["3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v3 with: name: godata_server-${{ matrix.target }} @@ -122,7 +116,7 @@ jobs: target: [x86_64-apple-darwin] python-version: ["3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v3 with: name: godata_server-${{ matrix.target }} @@ -154,7 +148,7 @@ jobs: outputs: tag: ${{ steps.get_tag.outputs.tag }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: get commit tag id: get_tag uses: olegtarasov/get-tag@v2.1.2 @@ -204,7 +198,7 @@ jobs: runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: build and publish to pypi uses: JRubics/poetry-publish@v1.17 with: From e39b51a5eca43ec1022a9b1254fee0b3c85feb3b Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Wed, 7 Feb 2024 16:18:32 -0800 Subject: [PATCH 06/10] Update cargo --- .github/workflows/ci.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c92aec..00111a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,14 +34,11 @@ jobs: with: toolchain: stable targets: ${{ matrix.target }} - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target ${{ matrix.target }} + - run: cargo build --release --target ${{ matrix.target }} + - name: zip artifact run: zip -j godata_server-${{ matrix.target }}.zip target/${{ matrix.target }}/release/godata_server - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: godata_server-${{ matrix.target }} path: godata_server-${{ matrix.target }}.zip @@ -63,7 +60,7 @@ jobs: - name: zip artifact run: zip -j godata_server-${{ matrix.target }}.zip target/${{ matrix.target }}/release/godata_server - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: godata_server-${{ matrix.target }} path: godata_server-${{ matrix.target }}.zip From 98af5a421c742a2231fdce1a5c511e6a0fc19ae2 Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Wed, 7 Feb 2024 16:25:35 -0800 Subject: [PATCH 07/10] Use cross for targeting aarch64 --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00111a8..a7de694 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,8 @@ jobs: with: toolchain: stable targets: ${{ matrix.target }} - - run: cargo build --release --target ${{ matrix.target }} + - run: cargo install cross --git https://github.com/cross-rs/cross + - run: cross build --release --target ${{ matrix.target }} - name: zip artifact run: zip -j godata_server-${{ matrix.target }}.zip target/${{ matrix.target }}/release/godata_server @@ -56,7 +57,8 @@ jobs: with: toolchain: stable targets: ${{ matrix.target }} - - run: cargo build --release --target ${{ matrix.target }} + - run: cargo install cross --git https://github.com/cross-rs/cross + - run: cross build --release --target ${{ matrix.target }} - name: zip artifact run: zip -j godata_server-${{ matrix.target }}.zip target/${{ matrix.target }}/release/godata_server From 0674638331ba40855551192d69cd7e9f815765ce Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Wed, 7 Feb 2024 16:34:44 -0800 Subject: [PATCH 08/10] More CI updates --- .github/workflows/ci.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7de694..3902362 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,8 +57,7 @@ jobs: with: toolchain: stable targets: ${{ matrix.target }} - - run: cargo install cross --git https://github.com/cross-rs/cross - - run: cross build --release --target ${{ matrix.target }} + - run: cargo build --release --target ${{ matrix.target }} - name: zip artifact run: zip -j godata_server-${{ matrix.target }}.zip target/${{ matrix.target }}/release/godata_server @@ -79,7 +78,7 @@ jobs: python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: godata_server-${{ matrix.target }} path: server @@ -116,7 +115,7 @@ jobs: python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: godata_server-${{ matrix.target }} path: server @@ -215,7 +214,7 @@ jobs: - name: "Build Changelog" id: build_changelog uses: mikepenz/release-changelog-builder-action@v4.1.1 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 - uses: ncipollo/release-action@v1.13.0 with: token: ${{ secrets.RELEASE_TOKEN }} From 6ba960e97ed9f2d3b724368390ce619b2192df94 Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Wed, 7 Feb 2024 16:39:04 -0800 Subject: [PATCH 09/10] Fix tokio version in Cargo --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index eb97fd2..5317597 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ serde = {version = "1.0.188", features = ["derive"]} serde_json = "1.0.106" sled = "0.34.7" sysinfo = "0.30.5" -tokio = {features = ["full"]} +tokio = {version = "1.36.0", features = ["full"]} tokio-stream = { version = "0.1.14", features = ["net"] } uuid = { version = "1.5.0", features = ["v4"] } warp = "0.3.6" From 659a07a6da6413c4abcfeafeb21459f556f1e318 Mon Sep 17 00:00:00 2001 From: Patrick Wells Date: Wed, 7 Feb 2024 16:41:11 -0800 Subject: [PATCH 10/10] Update setup-python action --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3902362..8d725b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: - name: unzip artifact run: unzip -j server/godata_server-${{ matrix.target }}.zip -d server - name: install python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Poetry @@ -123,7 +123,7 @@ jobs: run: unzip -j server/godata_server-${{ matrix.target }}.zip -d server - name: install python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Poetry