From 6b9ff574f8d848b73e8a3e823e7a48e3d6bfda35 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sat, 11 Jan 2025 23:18:09 -0300 Subject: [PATCH 01/44] misc: Add typehints to safe type conversion functions --- backend/utils/database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/utils/database.py b/backend/utils/database.py index e148fe39c..1bf87ae5f 100644 --- a/backend/utils/database.py +++ b/backend/utils/database.py @@ -31,7 +31,7 @@ def json_array_contains_value( return func.json_contains(column, value) -def safe_float(value, default=0.0): +def safe_float(value: Any, default: float = 0.0) -> float: """Safely convert a value to float, returning default if conversion fails.""" try: return float(value) @@ -39,7 +39,7 @@ def safe_float(value, default=0.0): return default -def safe_int(value, default=0): +def safe_int(value: Any, default: int = 0) -> int: """Safely convert a value to int, returning default if conversion fails.""" try: return int(value) From d837070ad0387eee9c93c4e483ade08ecbc07fc7 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sun, 12 Jan 2025 00:25:35 -0300 Subject: [PATCH 02/44] fix: SocketIO JSON dumps with sane defaults Similar to how `engineio` provides a JSON-compatible module, this change adds a custom JSON encoder with support for additional types. Changing SocketIO's JSON module fixes the encoding issue when the scanning process tried to send a datetime, failing and the frontend not displaying the scanned game (commonly, when it had sibling games) --- backend/handler/socket_handler.py | 2 ++ backend/utils/json.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 backend/utils/json.py diff --git a/backend/handler/socket_handler.py b/backend/handler/socket_handler.py index a3392fb14..ac274c508 100644 --- a/backend/handler/socket_handler.py +++ b/backend/handler/socket_handler.py @@ -1,5 +1,6 @@ import socketio # type: ignore from config import REDIS_URL +from utils import json as json_module class SocketHandler: @@ -7,6 +8,7 @@ class SocketHandler: self.socket_server = socketio.AsyncServer( cors_allowed_origins="*", async_mode="asgi", + json=json_module, logger=False, engineio_logger=False, client_manager=socketio.AsyncRedisManager(str(REDIS_URL)), diff --git a/backend/utils/json.py b/backend/utils/json.py new file mode 100644 index 000000000..5d537b22a --- /dev/null +++ b/backend/utils/json.py @@ -0,0 +1,29 @@ +"""JSON-compatible module with sane defaults. + +Inspiration taken from `python-engineio`. +https://github.com/miguelgrinberg/python-engineio/blob/main/src/engineio/json.py +""" + +import datetime +import decimal +import json +import uuid +from json import * # noqa: F401, F403 +from json import dumps as __original_dumps +from typing import Any + + +class DefaultJSONEncoder(json.JSONEncoder): + """Custom JSON encoder that supports encoding additional types.""" + + def default(self, o: Any) -> Any: + if isinstance(o, (datetime.date, datetime.datetime, datetime.time)): + return o.isoformat() + if isinstance(o, (decimal.Decimal, uuid.UUID)): + return str(o) + return super().default(o) + + +def dumps(*args: Any, **kwargs: Any) -> str: # type: ignore[no-redef] + kwargs.setdefault("cls", DefaultJSONEncoder) + return __original_dumps(*args, **kwargs) From 978a40e30513d28a0436b3576df762a634fb8734 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Sun, 12 Jan 2025 10:20:18 -0500 Subject: [PATCH 03/44] [ROMM-1297] Fix loading psx mednafen core --- frontend/src/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 4827cba1d..9c26d0ccc 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -360,7 +360,7 @@ const _EJS_CORES_MAP = { "game-boy-micro": ["mgba"], gbc: ["gambatte", "mgba"], "pc-fx": ["mednafen_pcfx"], - ps: ["pcsx_rearmed", "mednafen_psx"], + ps: ["pcsx_rearmed", "mednafen_psx_hw"], psp: ["ppsspp"], segacd: ["genesis_plus_gx", "picodrive"], // sega32: ["picodrive"], // Broken: https://github.com/EmulatorJS/EmulatorJS/issues/579 From 245e22541636b743e9c16eb0cf49b1f994dae611 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sun, 12 Jan 2025 02:00:29 -0300 Subject: [PATCH 04/44] misc: Upgrade Poetry to v2.0 Release notes: https://python-poetry.org/blog/announcing-poetry-2.0.0/ --- poetry.lock | 147 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 109 +++++++++++++++++++----------------- 2 files changed, 201 insertions(+), 55 deletions(-) diff --git a/poetry.lock b/poetry.lock index dba2106fa..5b190d436 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. [[package]] name = "alembic" @@ -6,6 +6,7 @@ version = "1.13.1" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, @@ -25,6 +26,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -36,6 +38,7 @@ version = "4.7.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, @@ -57,6 +60,8 @@ version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" optional = false python-versions = ">=3.6" +groups = ["dev"] +markers = "platform_system == \"Darwin\"" files = [ {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, @@ -68,6 +73,7 @@ version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, @@ -83,6 +89,7 @@ version = "1.3.2" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "Authlib-1.3.2-py2.py3-none-any.whl", hash = "sha256:ede026a95e9f5cdc2d4364a52103f5405e75aa156357e831ef2bfd0bc5094dfc"}, {file = "authlib-1.3.2.tar.gz", hash = "sha256:4b16130117f9eb82aa6eec97f6dd4673c3f960ac0283ccdae2897ee4bc030ba2"}, @@ -97,6 +104,7 @@ version = "4.2.1" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "bcrypt-4.2.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:1340411a0894b7d3ef562fb233e4b6ed58add185228650942bdc885362f32c17"}, {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ee315739bc8387aa36ff127afc99120ee452924e0df517a8f3e4c0187a0f5f"}, @@ -135,6 +143,7 @@ version = "0.23.1" description = "The bidirectional mapping library for Python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"}, {file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"}, @@ -146,6 +155,8 @@ version = "1.1.0" description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" +groups = ["main"] +markers = "platform_python_implementation == \"CPython\"" files = [ {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, @@ -238,6 +249,8 @@ version = "1.1.0.0" description = "Python CFFI bindings to the Brotli library" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "platform_python_implementation == \"PyPy\"" files = [ {file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"}, {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"}, @@ -277,6 +290,7 @@ version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, @@ -288,6 +302,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -357,6 +372,7 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {dev = "implementation_name == \"pypy\""} [package.dependencies] pycparser = "*" @@ -367,6 +383,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -381,6 +398,7 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -392,6 +410,7 @@ version = "0.2.2" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, @@ -409,6 +428,7 @@ version = "1.0.1" description = "Parse and use crontab schedules in Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "crontab-1.0.1.tar.gz", hash = "sha256:89477e3f93c81365e738d5ee2659509e6373bb2846de13922663e79aa74c6b91"}, ] @@ -419,6 +439,7 @@ version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" +groups = ["main"] files = [ {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, @@ -470,6 +491,7 @@ version = "1.8.9" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "debugpy-1.8.9-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:cfe1e6c6ad7178265f74981edf1154ffce97b69005212fbc90ca22ddfe3d017e"}, {file = "debugpy-1.8.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada7fb65102a4d2c9ab62e8908e9e9f12aed9d76ef44880367bc9308ebe49a0f"}, @@ -505,6 +527,7 @@ version = "5.1.1" description = "Decorators for Humans" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, @@ -516,6 +539,7 @@ version = "2.10.1" description = "Emoji for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "emoji-2.10.1-py2.py3-none-any.whl", hash = "sha256:11fb369ea79d20c14efa4362c732d67126df294a7959a2c98bfd7447c12a218e"}, {file = "emoji-2.10.1.tar.gz", hash = "sha256:16287283518fb7141bde00198f9ffff4e1c1cb570efb68b2f1ec50975c3a581d"}, @@ -530,6 +554,7 @@ version = "2.1.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, @@ -544,6 +569,7 @@ version = "2.26.1" description = "Python implementation of redis API, can be used for testing purposes." optional = false python-versions = "<4.0,>=3.7" +groups = ["test"] files = [ {file = "fakeredis-2.26.1-py3-none-any.whl", hash = "sha256:68a5615d7ef2529094d6958677e30a6d30d544e203a5ab852985c19d7ad57e32"}, {file = "fakeredis-2.26.1.tar.gz", hash = "sha256:69f4daafe763c8014a6dbf44a17559c46643c95447b3594b3975251a171b806d"}, @@ -566,6 +592,7 @@ version = "0.115.6" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, @@ -586,6 +613,7 @@ version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -600,6 +628,8 @@ version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" files = [ {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, @@ -686,6 +716,7 @@ version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, @@ -707,6 +738,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -718,6 +750,7 @@ version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -739,6 +772,7 @@ version = "0.27.2" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, @@ -764,6 +798,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -778,6 +813,7 @@ version = "1.0.0" description = "deflate64 compression/decompression library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a90c0bdf4a7ecddd8a64cc977181810036e35807f56b0bcacee9abb0fcfd18dc"}, {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:57fe7c14aebf1c5a74fc3b70d355be1280a011521a76aa3895486e62454f4242"}, @@ -845,6 +881,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -856,6 +893,7 @@ version = "0.13.13" description = "IPython-enabled pdb" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, @@ -871,6 +909,7 @@ version = "6.29.5" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, @@ -904,6 +943,7 @@ version = "8.30.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "ipython-8.30.0-py3-none-any.whl", hash = "sha256:85ec56a7e20f6c38fce7727dcca699ae4ffc85985aa7b23635a8008f918ae321"}, {file = "ipython-8.30.0.tar.gz", hash = "sha256:cb0a405a306d2995a5cbb9901894d240784a9f341394c6ba3f4fe8c6eb89ff6e"}, @@ -940,6 +980,7 @@ version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -951,6 +992,7 @@ version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, @@ -970,6 +1012,7 @@ version = "0.9.0" description = "The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "joserfc-0.9.0-py3-none-any.whl", hash = "sha256:4026bdbe2c196cd40574e916fa1e28874d99649412edaab0e373dec3077153fb"}, {file = "joserfc-0.9.0.tar.gz", hash = "sha256:eebca7f587b1761ce43a98ffd5327f2b600b9aa5bb0a77b947687f503ad43bc0"}, @@ -987,6 +1030,7 @@ version = "8.6.3" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, @@ -1009,6 +1053,7 @@ version = "5.7.2" description = "Jupyter core package. A base package on which Jupyter projects rely." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, @@ -1029,6 +1074,7 @@ version = "1.3.8" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"}, {file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"}, @@ -1048,6 +1094,7 @@ version = "1.1.11" description = "Python MariaDB extension" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "mariadb-1.1.11-cp310-cp310-win32.whl", hash = "sha256:dbc4cf0e302ca82d46f9431a0b04f048e9c21ee56d6f3162c29605f84d63b40c"}, {file = "mariadb-1.1.11-cp310-cp310-win_amd64.whl", hash = "sha256:579420293fa790d5ae0a6cb4bdb7e8be8facc2ceefb6123c2b0e8042b3fa725d"}, @@ -1071,6 +1118,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1141,6 +1189,7 @@ version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, @@ -1155,6 +1204,7 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -1256,6 +1306,7 @@ version = "0.2.3" description = "multi volume file wrapper library" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "multivolumefile-0.2.3-py3-none-any.whl", hash = "sha256:237f4353b60af1703087cf7725755a1f6fcaeeea48421e1896940cd1c920d678"}, {file = "multivolumefile-0.2.3.tar.gz", hash = "sha256:a0648d0aafbc96e59198d5c17e9acad7eb531abea51035d08ce8060dcad709d6"}, @@ -1272,6 +1323,7 @@ version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, @@ -1324,6 +1376,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1335,6 +1388,7 @@ version = "9.1.0" description = "A self-contained Python driver for communicating with MySQL servers, using an API that is compliant with the Python Database API Specification v2.0 (PEP 249)." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "mysql-connector-python-9.1.0.tar.gz", hash = "sha256:346261a2aeb743a39cf66ba8bde5e45931d313b76ce0946a69a6d1187ec7d279"}, {file = "mysql_connector_python-9.1.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:dcdcf380d07b9ca6f18a95e9516a6185f2ab31a53d290d5e698e77e59c043c9e"}, @@ -1377,6 +1431,7 @@ version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -1388,6 +1443,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "test"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1399,6 +1455,7 @@ version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, @@ -1414,6 +1471,7 @@ version = "1.7.4" description = "comprehensive password hashing framework supporting over 30 schemes" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, @@ -1434,6 +1492,8 @@ version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -1448,6 +1508,7 @@ version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, @@ -1545,6 +1606,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1561,6 +1623,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1576,6 +1639,7 @@ version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" +groups = ["dev"] files = [ {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, @@ -1590,6 +1654,7 @@ version = "0.2.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" +groups = ["main", "test"] files = [ {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, @@ -1681,6 +1746,7 @@ version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, @@ -1700,6 +1766,7 @@ files = [ {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, ] +markers = {main = "sys_platform != \"cygwin\""} [package.extras] dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] @@ -1711,6 +1778,7 @@ version = "3.2.3" description = "PostgreSQL database adapter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "psycopg-3.2.3-py3-none-any.whl", hash = "sha256:644d3973fe26908c73d4be746074f6e5224b03c1101d302d9a53bf565ad64907"}, {file = "psycopg-3.2.3.tar.gz", hash = "sha256:a5764f67c27bec8bfac85764d23c534af2c27b893550377e37ce59c12aac47a2"}, @@ -1735,6 +1803,8 @@ version = "3.2.3" description = "PostgreSQL database adapter for Python -- C optimisation distribution" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "implementation_name != \"pypy\"" files = [ {file = "psycopg_c-3.2.3.tar.gz", hash = "sha256:06ae7db8eaec1a3845960fa7f997f4ccdb1a7a7ab8dc593a680bcc74e1359671"}, ] @@ -1745,6 +1815,8 @@ version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -1756,6 +1828,7 @@ version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, @@ -1770,6 +1843,7 @@ version = "1.0.0rc2" description = "Pure python 7-zip library" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "py7zr-1.0.0rc2-py3-none-any.whl", hash = "sha256:4eba876d6018ccc7aead07d5f10795151be5b272289078b899f199b27d2dae41"}, {file = "py7zr-1.0.0rc2.tar.gz", hash = "sha256:8a62e7c96050eb357ab5a41a7863078931733e235e796468b4d5e22bb0888bf0"}, @@ -1800,6 +1874,7 @@ version = "1.0.2" description = "bcj filter library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7bff28d97e47047d69a4ac6bf59adda738cf1d00adde8819117fdb65d966bdbc"}, {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:198e0b4768b4025eb3309273d7e81dc53834b9a50092be6e0d9b3983cfd35c35"}, @@ -1854,10 +1929,12 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {dev = "implementation_name == \"pypy\""} [[package]] name = "pycryptodomex" @@ -1865,6 +1942,7 @@ version = "3.21.0" description = "Cryptographic library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] files = [ {file = "pycryptodomex-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbeb84a399373df84a69e0919c1d733b89e049752426041deeb30d68e9867822"}, {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a192fb46c95489beba9c3f002ed7d93979423d1b2a53eab8771dbb1339eb3ddd"}, @@ -1906,6 +1984,7 @@ version = "2.10.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, @@ -1926,6 +2005,7 @@ version = "2.27.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, @@ -2038,6 +2118,7 @@ version = "7.0.7" description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydash-7.0.7-py3-none-any.whl", hash = "sha256:c3c5b54eec0a562e0080d6f82a14ad4d5090229847b7e554235b5c1558c745e1"}, {file = "pydash-7.0.7.tar.gz", hash = "sha256:cc935d5ac72dd41fb4515bdf982e7c864c8b5eeea16caffbab1936b849aaa49a"}, @@ -2055,6 +2136,7 @@ version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -2069,6 +2151,7 @@ version = "1.1.0" description = "PPMd compression/decompression library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5cd428715413fe55abf79dc9fc54924ba7e518053e1fc0cbdf80d0d99cf1442"}, {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e96cc43f44b7658be2ea764e7fa99c94cb89164dbb7cdf209178effc2168319"}, @@ -2155,6 +2238,7 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -2175,6 +2259,7 @@ version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, @@ -2193,6 +2278,7 @@ version = "1.1.5" description = "pytest plugin that allows you to add environment variables." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30"}, {file = "pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf"}, @@ -2210,6 +2296,7 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -2227,6 +2314,7 @@ version = "0.13.2" description = "A pytest plugin that allows you recording of network interactions via VCR.py" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "pytest_recording-0.13.2-py3-none-any.whl", hash = "sha256:3820fe5743d1ac46e807989e11d073cb776a60bdc544cf43ebca454051b22d13"}, {file = "pytest_recording-0.13.2.tar.gz", hash = "sha256:000c3babbb466681457fd65b723427c1779a0c6c17d9e381c3142a701e124877"}, @@ -2246,6 +2334,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2260,6 +2349,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -2274,6 +2364,7 @@ version = "4.10.1" description = "Engine.IO server and client for Python" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "python_engineio-4.10.1-py3-none-any.whl", hash = "sha256:445a94004ec8034960ab99e7ce4209ec619c6e6b6a12aedcb05abeab924025c0"}, {file = "python_engineio-4.10.1.tar.gz", hash = "sha256:166cea8dd7429638c5c4e3a4895beae95196e860bc6f29ed0b9fe753d1ef2072"}, @@ -2293,6 +2384,7 @@ version = "0.4.27" description = "File type identification using libmagic" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"}, {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"}, @@ -2304,6 +2396,7 @@ version = "0.0.18" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"}, {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"}, @@ -2315,6 +2408,7 @@ version = "5.11.1" description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-socketio-5.11.1.tar.gz", hash = "sha256:bbcbd758ed8c183775cb2853ba001361e2fa018babf5cbe11a5b77e91c2ec2a2"}, {file = "python_socketio-5.11.1-py3-none-any.whl", hash = "sha256:f1a0228b8b1fbdbd93fbbedd821ebce0ef54b2b5bf6e98fcf710deaa7c574259"}, @@ -2335,6 +2429,8 @@ version = "308" description = "Python for Window Extensions" optional = false python-versions = "*" +groups = ["dev"] +markers = "platform_python_implementation != \"PyPy\" and sys_platform == \"win32\"" files = [ {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, @@ -2362,6 +2458,7 @@ version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, @@ -2422,6 +2519,7 @@ version = "26.2.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, @@ -2543,6 +2641,7 @@ version = "0.16.2" description = "Python bindings to Zstandard (zstd) compression library." optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "pyzstd-0.16.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:637376c8f8cbd0afe1cab613f8c75fd502bd1016bf79d10760a2d5a00905fe62"}, {file = "pyzstd-0.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e7a7118cbcfa90ca2ddbf9890c7cb582052a9a8cf2b7e2c1bbaf544bee0f16a"}, @@ -2635,6 +2734,7 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, @@ -2650,6 +2750,7 @@ version = "1.16.2" description = "RQ is a simple, lightweight, library for creating background jobs, and processing them." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "rq-1.16.2-py3-none-any.whl", hash = "sha256:52e619f6cb469b00e04da74305045d244b75fecb2ecaa4f26422add57d3c5f09"}, {file = "rq-1.16.2.tar.gz", hash = "sha256:5c5b9ad5fbaf792b8fada25cc7627f4d206a9a4455aced371d4f501cc3f13b34"}, @@ -2665,6 +2766,7 @@ version = "0.13.1" description = "Provides job scheduling capabilities to RQ (Redis Queue)" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "rq-scheduler-0.13.1.tar.gz", hash = "sha256:89d6a18f215536362b22c0548db7dbb8678bc520c18dc18a82fd0bb2b91695ce"}, {file = "rq_scheduler-0.13.1-py2.py3-none-any.whl", hash = "sha256:c2b19c3aedfc7de4d405183c98aa327506e423bf4cdc556af55aaab9bbe5d1a1"}, @@ -2682,6 +2784,7 @@ version = "2.19.2" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84"}, {file = "sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d"}, @@ -2736,6 +2839,7 @@ version = "1.1.0" description = "Simple WebSocket server and client for Python" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c"}, {file = "simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"}, @@ -2754,6 +2858,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2765,6 +2870,7 @@ version = "6.4.0" description = "Utils for streaming large files (S3, HDFS, GCS, Azure Blob Storage, gzip, bz2...)" optional = false python-versions = ">=3.6,<4.0" +groups = ["main"] files = [ {file = "smart_open-6.4.0-py3-none-any.whl", hash = "sha256:8d3ef7e6997e8e42dd55c74166ed21e6ac70664caa32dd940b26d54a8f6b4142"}, {file = "smart_open-6.4.0.tar.gz", hash = "sha256:be3c92c246fbe80ebce8fbacb180494a481a77fcdcb7c1aadb2ea5b9c2bee8b9"}, @@ -2786,6 +2892,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2797,6 +2904,7 @@ version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" optional = false python-versions = "*" +groups = ["test"] files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, @@ -2808,6 +2916,7 @@ version = "2.0.1733540710" description = "offset-free paging for sqlalchemy" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "sqlakeyset-2.0.1733540710-py3-none-any.whl", hash = "sha256:952c5179484010e1f635697c6799760e56cfb559e20ed4e81289ae7840da8161"}, {file = "sqlakeyset-2.0.1733540710.tar.gz", hash = "sha256:1dc43df81cac6b992acde5605d7182d7f1f6bf9d97bbfd7ecc0272459557940a"}, @@ -2825,6 +2934,7 @@ version = "2.0.36" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, @@ -2923,6 +3033,7 @@ version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, @@ -2942,6 +3053,7 @@ version = "0.41.3" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, @@ -2959,6 +3071,7 @@ version = "3.0.0" description = "Starlette middleware implementing Double Submit Cookie technique to mitigate CSRF" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "starlette_csrf-3.0.0-py3-none-any.whl", hash = "sha256:aac29b366e83621d3fc56be690866e16f3c56df91ab5e184b77950540a4e2761"}, {file = "starlette_csrf-3.0.0.tar.gz", hash = "sha256:7afaca8c72cc3c726e5942778af53454607ca3e653fd86cd75ee35d8cd1cfa77"}, @@ -2974,6 +3087,7 @@ version = "1.19.0" description = "Streaming parser for multipart/form-data" optional = false python-versions = "^3.9" +groups = ["main"] files = [] develop = false @@ -2992,6 +3106,7 @@ version = "1.7.0" description = "module to create simple ASCII tables" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"}, {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, @@ -3003,6 +3118,7 @@ version = "6.4.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"}, @@ -3023,6 +3139,7 @@ version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -3038,6 +3155,7 @@ version = "1.16.0.20240331" description = "Typing stubs for cffi" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "types-cffi-1.16.0.20240331.tar.gz", hash = "sha256:b8b20d23a2b89cfed5f8c5bc53b0cb8677c3aac6d970dbc771e28b9c698f5dee"}, {file = "types_cffi-1.16.0.20240331-py3-none-any.whl", hash = "sha256:a363e5ea54a4eb6a4a105d800685fde596bc318089b025b27dee09849fe41ff0"}, @@ -3052,6 +3170,7 @@ version = "0.4.15.20240311" description = "Typing stubs for colorama" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "types-colorama-0.4.15.20240311.tar.gz", hash = "sha256:a28e7f98d17d2b14fb9565d32388e419f4108f557a7d939a66319969b2b99c7a"}, {file = "types_colorama-0.4.15.20240311-py3-none-any.whl", hash = "sha256:6391de60ddc0db3f147e31ecb230006a6823e81e380862ffca1e4695c13a0b8e"}, @@ -3063,6 +3182,7 @@ version = "1.7.7.20240819" description = "Typing stubs for passlib" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "types-passlib-1.7.7.20240819.tar.gz", hash = "sha256:8fc8df71623845032293d5cf7f8091f0adfeba02d387a2888684b8413f14b3d0"}, {file = "types_passlib-1.7.7.20240819-py3-none-any.whl", hash = "sha256:c4d299083497b66e12258c7b77c08952574213fdf7009da3135d8181a6a25f23"}, @@ -3074,6 +3194,7 @@ version = "24.1.0.20240722" description = "Typing stubs for pyOpenSSL" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39"}, {file = "types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54"}, @@ -3089,6 +3210,7 @@ version = "6.0.12.20240917" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, @@ -3100,6 +3222,7 @@ version = "4.6.0.20241004" description = "Typing stubs for redis" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "types-redis-4.6.0.20241004.tar.gz", hash = "sha256:5f17d2b3f9091ab75384153bfa276619ffa1cf6a38da60e10d5e6749cc5b902e"}, {file = "types_redis-4.6.0.20241004-py3-none-any.whl", hash = "sha256:ef5da68cb827e5f606c8f9c0b49eeee4c2669d6d97122f301d3a55dc6a63f6ed"}, @@ -3115,6 +3238,7 @@ version = "75.6.0.20241126" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "types_setuptools-75.6.0.20241126-py3-none-any.whl", hash = "sha256:aaae310a0e27033c1da8457d4d26ac673b0c8a0de7272d6d4708e263f2ea3b9b"}, {file = "types_setuptools-75.6.0.20241126.tar.gz", hash = "sha256:7bf25ad4be39740e469f9268b6beddda6e088891fa5a27e985c6ce68bf62ace0"}, @@ -3126,6 +3250,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -3137,6 +3262,8 @@ version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, @@ -3148,6 +3275,7 @@ version = "1.3.8" description = "ASCII transliterations of Unicode text" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"}, {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, @@ -3159,6 +3287,8 @@ version = "1.26.20" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main", "test"] +markers = "platform_python_implementation == \"PyPy\"" files = [ {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, @@ -3175,6 +3305,8 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main", "test"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, @@ -3192,6 +3324,7 @@ version = "0.29.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, @@ -3210,6 +3343,7 @@ version = "6.0.2" description = "Automatically mock your HTTP interactions to simplify and speed up testing" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "vcrpy-6.0.2-py2.py3-none-any.whl", hash = "sha256:40370223861181bc76a5e5d4b743a95058bb1ad516c3c08570316ab592f56cad"}, {file = "vcrpy-6.0.2.tar.gz", hash = "sha256:88e13d9111846745898411dbc74a75ce85870af96dd320d75f1ee33158addc09"}, @@ -3233,6 +3367,7 @@ version = "4.0.2" description = "Filesystem events monitoring" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, @@ -3280,6 +3415,7 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -3291,6 +3427,7 @@ version = "12.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, @@ -3372,6 +3509,7 @@ version = "1.17.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, @@ -3446,6 +3584,7 @@ version = "1.2.0" description = "WebSockets state-machine based protocol implementation" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, @@ -3460,6 +3599,7 @@ version = "1.18.3" description = "Yet another URL library" optional = false python-versions = ">=3.9" +groups = ["main", "test"] files = [ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, @@ -3556,6 +3696,7 @@ version = "0.2.0" description = "Extract Deflate64 ZIP archives with Python's zipfile API." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "zipfile-deflate64-0.2.0.tar.gz", hash = "sha256:875a3299de102edf1c17f8cafcc528b1ca80b62dc4814b9cb56867ec59fbfd18"}, {file = "zipfile_deflate64-0.2.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:5ed1c3edc08e8da8fe646b23105e2840f305ce2a75360aa0df7523c1263f43aa"}, @@ -3576,6 +3717,6 @@ files = [ ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" -content-hash = "8b0d4ecd3191f13860cbfc3211112bd66966388afdcb31582c8138f9100e9b05" +content-hash = "b4cdb77d1cd6aa61bc991c0f5bc88effa89366518ecf27f27232983fa41f18a3" diff --git a/pyproject.toml b/pyproject.toml index 191660884..910388071 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,59 +1,64 @@ -[tool.poetry] -package-mode = false +[project] name = "romm" -version = "0.0.1" description = "A beautiful, powerful, self-hosted rom manager" license = "GNU AGPLv3" -repository = "https://github.com/rommapp/romm" -authors = ["Zurdi ", "Arcane "] +readme = "README.md" +authors = [ + { name = "Zurdi", email = "zurdi@romm.app" }, + { name = "Arcane", email = "arcane@romm.app" }, + { name = "Adamantike", email = "adamantike@romm.app" }, +] +requires-python = "^3.12" +dependencies = [ + "PyYAML == 6.0.1", + "SQLAlchemy[mariadb-connector,mysql-connector,postgresql-psycopg] ~= 2.0", + "Unidecode == 1.3.8", + "alembic == 1.13.1", + "anyio ~= 4.4", + "authlib ~= 1.3", + "certifi == 2024.07.04", + "colorama ~= 0.4", + "emoji == 2.10.1", + "fastapi == 0.115.6", + "gunicorn == 22.0.0", + "httpx ~= 0.27", + "itsdangerous ~= 2.1", + "joserfc ~= 0.9", + "passlib[bcrypt] ~= 1.7", + "pillow ~= 10.3", + "psycopg[c] ~= 3.2", + "py7zr == 1.0.0rc2", + "pydash ~= 7.0", + "python-dotenv == 1.0.1", + "python-magic ~= 0.4", + "python-multipart ~= 0.0.18", + "python-socketio == 5.11.1", + "redis ~= 5.0", + "rq ~= 1.16", + "rq-scheduler ~= 0.13", + "sentry-sdk ~= 2.19", + "sqlakeyset ~= 2.0", + "starlette-csrf ~= 3.0", + # TODO: Move back to official releases once the following PR is merged and released: + # https://github.com/python-poetry/poetry-core/pull/803 + "streaming-form-data @ git+https://github.com/gantoine/streaming-form-data.git@b8a49ba", + "types-colorama ~= 0.4", + "types-passlib ~= 1.7", + "types-pyyaml ~= 6.0", + "types-redis ~= 4.6", + "uvicorn == 0.29.0", + "watchdog ~= 4.0", + "websockets == 12.0", + "yarl ~= 1.14", + "zipfile-deflate64 ~= 0.2", +] -[tool.poetry.dependencies] -python = "^3.12" -anyio = "^4.4" -fastapi = "0.115.6" -uvicorn = "0.29.0" -gunicorn = "22.0.0" -websockets = "12.0" -python-socketio = "5.11.1" -psycopg = { version = "^3.2", extras = ["c"] } -SQLAlchemy = { version = "^2.0.30", extras = [ - "mariadb-connector", - "mysql-connector", - "postgresql-psycopg", -] } -alembic = "1.13.1" -PyYAML = "6.0.1" -Unidecode = "1.3.8" -emoji = "2.10.1" -python-dotenv = "1.0.1" -sqlakeyset = "^2.0.1708907391" -pydash = "^7.0.7" -rq = "^1.16.1" -redis = "^5.0" -passlib = { extras = ["bcrypt"], version = "^1.7.4" } -itsdangerous = "^2.1.2" -rq-scheduler = "^0.13.1" -starlette-csrf = "^3.0.0" -httpx = "^0.27.0" -python-multipart = "^0.0.18" -watchdog = "^4.0.0" -yarl = "^1.14" -joserfc = "^0.9.0" -pillow = "^10.3.0" -certifi = "2024.07.04" -authlib = "^1.3.1" -python-magic = "^0.4.27" -py7zr = "1.0.0rc2" -sentry-sdk = "^2.19" -# TODO: Move back to official releases once the following PR is merged and released: -# https://github.com/python-poetry/poetry-core/pull/803 -streaming-form-data = { git = "https://github.com/gantoine/streaming-form-data.git", rev = "b8a49ba" } -zipfile-deflate64 = "^0.2.0" -colorama = "^0.4.6" -types-colorama = "^0.4" -types-passlib = "^1.7.7.20240311" -types-pyyaml = "^6.0.12.20240311" -types-redis = "^4.6.0.20240311 " +[project.urls] +Homepage = "https://romm.app/" +Source = "https://github.com/rommapp/romm" + +[tool.poetry] +package-mode = false [tool.poetry.group.test.dependencies] fakeredis = "^2.21.3" From b683934e0918d3b87cb469900d52ed1f8e22fa8a Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sun, 12 Jan 2025 13:55:50 -0300 Subject: [PATCH 05/44] misc: Migrate optional dependencies to standard pyproject format --- docker/Dockerfile | 2 +- poetry.lock | 286 ++++++++++++++++++++++++++-------------------- pyproject.toml | 24 ++-- 3 files changed, 172 insertions(+), 140 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index ad257a4dc..90282d554 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -55,7 +55,7 @@ RUN poetry install --no-ansi --no-cache --only main FROM backend-build AS backend-dev-build -RUN poetry install --no-ansi --no-cache +RUN poetry install --no-ansi --no-cache --all-extras # TODO: Upgrade Alpine to the same version as the other stages, when RAHasher is updated to work diff --git a/poetry.lock b/poetry.lock index 5b190d436..243caf80e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -58,10 +58,10 @@ trio = ["trio (>=0.26.1)"] name = "appnope" version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" -optional = false +optional = true python-versions = ">=3.6" -groups = ["dev"] -markers = "platform_system == \"Darwin\"" +groups = ["main"] +markers = "extra == \"dev\" and platform_system == \"Darwin\"" files = [ {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, @@ -71,9 +71,10 @@ files = [ name = "asttokens" version = "3.0.0" description = "Annotate AST trees with source code positions" -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, @@ -302,7 +303,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -372,7 +373,6 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] -markers = {dev = "implementation_name == \"pypy\""} [package.dependencies] pycparser = "*" @@ -398,7 +398,7 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev", "test"] +groups = ["main"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -408,9 +408,10 @@ files = [ name = "comm" version = "0.2.2" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, @@ -489,9 +490,10 @@ test-randomorder = ["pytest-randomly"] name = "debugpy" version = "1.8.9" description = "An implementation of the Debug Adapter Protocol for Python" -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "debugpy-1.8.9-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:cfe1e6c6ad7178265f74981edf1154ffce97b69005212fbc90ca22ddfe3d017e"}, {file = "debugpy-1.8.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada7fb65102a4d2c9ab62e8908e9e9f12aed9d76ef44880367bc9308ebe49a0f"}, @@ -525,9 +527,10 @@ files = [ name = "decorator" version = "5.1.1" description = "Decorators for Humans" -optional = false +optional = true python-versions = ">=3.5" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, @@ -552,9 +555,10 @@ dev = ["coverage", "coveralls", "pytest"] name = "executing" version = "2.1.0" description = "Get the currently executing AST node of a frame, and other information" -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, @@ -567,9 +571,10 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth name = "fakeredis" version = "2.26.1" description = "Python implementation of redis API, can be used for testing purposes." -optional = false +optional = true python-versions = "<4.0,>=3.7" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "fakeredis-2.26.1-py3-none-any.whl", hash = "sha256:68a5615d7ef2529094d6958677e30a6d30d544e203a5ab852985c19d7ad57e32"}, {file = "fakeredis-2.26.1.tar.gz", hash = "sha256:69f4daafe763c8014a6dbf44a17559c46643c95447b3594b3975251a171b806d"}, @@ -798,7 +803,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -879,9 +884,10 @@ test = ["pyannotate", "pytest"] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -optional = false +optional = true python-versions = ">=3.7" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -891,9 +897,10 @@ files = [ name = "ipdb" version = "0.13.13" description = "IPython-enabled pdb" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, @@ -907,9 +914,10 @@ ipython = {version = ">=7.31.1", markers = "python_version >= \"3.11\""} name = "ipykernel" version = "6.29.5" description = "IPython Kernel for Jupyter" -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, @@ -941,9 +949,10 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio name = "ipython" version = "8.30.0" description = "IPython: Productive Interactive Computing" -optional = false +optional = true python-versions = ">=3.10" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "ipython-8.30.0-py3-none-any.whl", hash = "sha256:85ec56a7e20f6c38fce7727dcca699ae4ffc85985aa7b23635a8008f918ae321"}, {file = "ipython-8.30.0.tar.gz", hash = "sha256:cb0a405a306d2995a5cbb9901894d240784a9f341394c6ba3f4fe8c6eb89ff6e"}, @@ -990,9 +999,10 @@ files = [ name = "jedi" version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." -optional = false +optional = true python-versions = ">=3.6" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, @@ -1028,9 +1038,10 @@ drafts = ["pycryptodome"] name = "jupyter-client" version = "8.6.3" description = "Jupyter protocol implementation and client libraries" -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, @@ -1051,9 +1062,10 @@ test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pyt name = "jupyter-core" version = "5.7.2" description = "Jupyter core package. A base package on which Jupyter projects rely." -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, @@ -1187,9 +1199,10 @@ files = [ name = "matplotlib-inline" version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, @@ -1204,7 +1217,7 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -1321,9 +1334,10 @@ type = ["mypy", "mypy-extensions"] name = "mypy" version = "1.13.0" description = "Optional static typing for Python" -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, @@ -1374,9 +1388,10 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -optional = false +optional = true python-versions = ">=3.5" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1429,9 +1444,10 @@ telemetry = ["opentelemetry-api (==1.18.0)", "opentelemetry-exporter-otlp-proto- name = "nest-asyncio" version = "1.6.0" description = "Patch asyncio to allow nested event loops" -optional = false +optional = true python-versions = ">=3.5" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -1443,7 +1459,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "test"] +groups = ["main"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1453,9 +1469,10 @@ files = [ name = "parso" version = "0.8.4" description = "A Python Parser" -optional = false +optional = true python-versions = ">=3.6" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, @@ -1490,10 +1507,10 @@ totp = ["cryptography"] name = "pexpect" version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." -optional = false +optional = true python-versions = "*" -groups = ["dev"] -markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +groups = ["main"] +markers = "extra == \"dev\" and (sys_platform != \"win32\" and sys_platform != \"emscripten\")" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -1604,9 +1621,10 @@ xmp = ["defusedxml"] name = "platformdirs" version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1621,9 +1639,10 @@ type = ["mypy (>=1.11.2)"] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" -optional = false +optional = true python-versions = ">=3.8" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1637,9 +1656,10 @@ testing = ["pytest", "pytest-benchmark"] name = "prompt-toolkit" version = "3.0.48" description = "Library for building powerful interactive command lines in Python" -optional = false +optional = true python-versions = ">=3.7.0" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, @@ -1654,7 +1674,7 @@ version = "0.2.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, @@ -1746,7 +1766,8 @@ version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main", "dev"] +groups = ["main"] +markers = "sys_platform != \"cygwin\" or extra == \"dev\"" files = [ {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, @@ -1766,7 +1787,6 @@ files = [ {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, ] -markers = {main = "sys_platform != \"cygwin\""} [package.extras] dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] @@ -1813,10 +1833,10 @@ files = [ name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" -optional = false +optional = true python-versions = "*" -groups = ["dev"] -markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +groups = ["main"] +markers = "extra == \"dev\" and (sys_platform != \"win32\" and sys_platform != \"emscripten\")" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -1826,9 +1846,10 @@ files = [ name = "pure-eval" version = "0.2.3" description = "Safely evaluate AST nodes without side effects" -optional = false +optional = true python-versions = "*" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, @@ -1929,12 +1950,11 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] -markers = {dev = "implementation_name == \"pypy\""} [[package]] name = "pycryptodomex" @@ -2134,9 +2154,10 @@ dev = ["black", "build", "coverage", "docformatter", "flake8", "flake8-black", " name = "pygments" version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -2236,9 +2257,10 @@ test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-benchm name = "pytest" version = "8.3.4" description = "pytest: simple powerful testing with Python" -optional = false +optional = true python-versions = ">=3.8" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -2257,9 +2279,10 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments name = "pytest-asyncio" version = "0.23.8" description = "Pytest support for asyncio" -optional = false +optional = true python-versions = ">=3.8" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, @@ -2276,9 +2299,10 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] name = "pytest-env" version = "1.1.5" description = "pytest plugin that allows you to add environment variables." -optional = false +optional = true python-versions = ">=3.8" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30"}, {file = "pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf"}, @@ -2294,9 +2318,10 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "pytest-mock (>=3.14)"] name = "pytest-mock" version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false +optional = true python-versions = ">=3.8" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -2312,9 +2337,10 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytest-recording" version = "0.13.2" description = "A pytest plugin that allows you recording of network interactions via VCR.py" -optional = false +optional = true python-versions = ">=3.7" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "pytest_recording-0.13.2-py3-none-any.whl", hash = "sha256:3820fe5743d1ac46e807989e11d073cb776a60bdc544cf43ebca454051b22d13"}, {file = "pytest_recording-0.13.2.tar.gz", hash = "sha256:000c3babbb466681457fd65b723427c1779a0c6c17d9e381c3142a701e124877"}, @@ -2334,7 +2360,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2427,10 +2453,10 @@ docs = ["sphinx"] name = "pywin32" version = "308" description = "Python for Window Extensions" -optional = false +optional = true python-versions = "*" -groups = ["dev"] -markers = "platform_python_implementation != \"PyPy\" and sys_platform == \"win32\"" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\" and sys_platform == \"win32\" and extra == \"dev\"" files = [ {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, @@ -2458,7 +2484,7 @@ version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, @@ -2517,9 +2543,10 @@ files = [ name = "pyzmq" version = "26.2.0" description = "Python bindings for 0MQ" -optional = false +optional = true python-versions = ">=3.7" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, @@ -2734,7 +2761,7 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, @@ -2858,7 +2885,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2902,9 +2929,10 @@ files = [ name = "sortedcontainers" version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -optional = false +optional = true python-versions = "*" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, @@ -3031,9 +3059,10 @@ sqlcipher = ["sqlcipher3_binary"] name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" -optional = false +optional = true python-versions = "*" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, @@ -3116,9 +3145,10 @@ files = [ name = "tornado" version = "6.4.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"}, @@ -3137,9 +3167,10 @@ files = [ name = "traitlets" version = "5.14.3" description = "Traitlets Python configuration system" -optional = false +optional = true python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -3250,7 +3281,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -3281,32 +3312,13 @@ files = [ {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, ] -[[package]] -name = "urllib3" -version = "1.26.20" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main", "test"] -markers = "platform_python_implementation == \"PyPy\"" -files = [ - {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, - {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, -] - -[package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - [[package]] name = "urllib3" version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" -groups = ["main", "test"] -markers = "platform_python_implementation != \"PyPy\"" +groups = ["main"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, @@ -3337,13 +3349,32 @@ h11 = ">=0.8" [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +[[package]] +name = "vcrpy" +version = "5.1.0" +description = "Automatically mock your HTTP interactions to simplify and speed up testing" +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation == \"PyPy\" and extra == \"test\"" +files = [ + {file = "vcrpy-5.1.0-py2.py3-none-any.whl", hash = "sha256:605e7b7a63dcd940db1df3ab2697ca7faf0e835c0852882142bafb19649d599e"}, + {file = "vcrpy-5.1.0.tar.gz", hash = "sha256:bbf1532f2618a04f11bce2a99af3a9647a32c880957293ff91e0a5f187b6b3d2"}, +] + +[package.dependencies] +PyYAML = "*" +wrapt = "*" +yarl = "*" + [[package]] name = "vcrpy" version = "6.0.2" description = "Automatically mock your HTTP interactions to simplify and speed up testing" -optional = false +optional = true python-versions = ">=3.8" -groups = ["test"] +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\" and extra == \"test\"" files = [ {file = "vcrpy-6.0.2-py2.py3-none-any.whl", hash = "sha256:40370223861181bc76a5e5d4b743a95058bb1ad516c3c08570316ab592f56cad"}, {file = "vcrpy-6.0.2.tar.gz", hash = "sha256:88e13d9111846745898411dbc74a75ce85870af96dd320d75f1ee33158addc09"}, @@ -3351,10 +3382,7 @@ files = [ [package.dependencies] PyYAML = "*" -urllib3 = [ - {version = "*", markers = "platform_python_implementation != \"PyPy\" and python_version >= \"3.10\""}, - {version = "<2", markers = "platform_python_implementation == \"PyPy\""}, -] +urllib3 = {version = "*", markers = "platform_python_implementation != \"PyPy\" and python_version >= \"3.10\""} wrapt = "*" yarl = "*" @@ -3413,9 +3441,10 @@ watchmedo = ["PyYAML (>=3.10)"] name = "wcwidth" version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" -optional = false +optional = true python-versions = "*" -groups = ["dev"] +groups = ["main"] +markers = "extra == \"dev\"" files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -3507,9 +3536,10 @@ files = [ name = "wrapt" version = "1.17.0" description = "Module for decorators, wrappers and monkey patching." -optional = false +optional = true python-versions = ">=3.8" -groups = ["test"] +groups = ["main"] +markers = "extra == \"test\"" files = [ {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, @@ -3599,7 +3629,7 @@ version = "1.18.3" description = "Yet another URL library" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, @@ -3716,7 +3746,11 @@ files = [ {file = "zipfile_deflate64-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:a16cd144c12de642f0ace1aeab7f50e7abc7492c81381faa2b17cc36f38272c4"}, ] +[extras] +dev = ["ipdb", "ipykernel", "mypy"] +test = ["fakeredis", "pytest", "pytest-asyncio", "pytest-env", "pytest-mock", "pytest-recording"] + [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "b4cdb77d1cd6aa61bc991c0f5bc88effa89366518ecf27f27232983fa41f18a3" +content-hash = "3c92517a9a6abe41cc7d465f843f4ba4e8043eb2fd9dd352dafcb8bae0a7dff9" diff --git a/pyproject.toml b/pyproject.toml index 910388071..cbb35242c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,22 +53,20 @@ dependencies = [ "zipfile-deflate64 ~= 0.2", ] +[project.optional-dependencies] +dev = ["ipdb ~= 0.13", "ipykernel ~= 6.29", "mypy ~= 1.13"] +test = [ + "fakeredis ~= 2.21", + "pytest ~= 8.3", + "pytest-asyncio ~= 0.23", + "pytest-env ~= 1.1", + "pytest-mock ~= 3.12", + "pytest-recording ~= 0.13", +] + [project.urls] Homepage = "https://romm.app/" Source = "https://github.com/rommapp/romm" [tool.poetry] package-mode = false - -[tool.poetry.group.test.dependencies] -fakeredis = "^2.21.3" -pytest = "^8.3" -pytest-env = "^1.1.3" -pytest-mock = "^3.12.0" -pytest-asyncio = "^0.23.5" -pytest-recording = "^0.13" - -[tool.poetry.group.dev.dependencies] -ipdb = "^0.13.13" -mypy = "^1.13" -ipykernel = "^6.29.4" From d0ba1d6e19e7bffc8f7724e25fa830272e7388ff Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Sun, 12 Jan 2025 14:32:44 -0500 Subject: [PATCH 06/44] [HOTFIX] Fix remove from continue playing --- backend/handler/database/roms_handler.py | 21 ++++++++++--------- .../src/components/common/Game/AdminMenu.vue | 4 +++- frontend/src/services/api/rom.ts | 2 +- frontend/src/stores/roms.ts | 8 +++++++ 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/backend/handler/database/roms_handler.py b/backend/handler/database/roms_handler.py index e9cca91aa..161fae962 100644 --- a/backend/handler/database/roms_handler.py +++ b/backend/handler/database/roms_handler.py @@ -276,18 +276,19 @@ class DBRomsHandler(DBBaseHandler): rom_user = self.get_rom_user_by_id(id) - if data.get("is_main_sibling", False): - rom = self.get_rom(rom_user.rom_id) + if not data.get("is_main_sibling", False): + return rom_user - session.execute( - update(RomUser) - .where( - and_( - RomUser.rom_id.in_(r.id for r in rom.sibling_roms), - RomUser.user_id == rom_user.user_id, - ) + rom = self.get_rom(rom_user.rom_id) + session.execute( + update(RomUser) + .where( + and_( + RomUser.rom_id.in_(r.id for r in rom.sibling_roms), + RomUser.user_id == rom_user.user_id, ) - .values(is_main_sibling=False) ) + .values(is_main_sibling=False) + ) return self.get_rom_user_by_id(id) diff --git a/frontend/src/components/common/Game/AdminMenu.vue b/frontend/src/components/common/Game/AdminMenu.vue index e6078680d..8d6cc6fe8 100644 --- a/frontend/src/components/common/Game/AdminMenu.vue +++ b/frontend/src/components/common/Game/AdminMenu.vue @@ -64,7 +64,7 @@ async function switchFromFavourites() { } await collectionApi .updateCollection({ collection: favCollection.value as Collection }) - .then(({ data }) => { + .then(() => { emitter?.emit("snackbarShow", { msg: `${props.rom.name} ${ collectionsStore.isFav(props.rom) ? "added to" : "removed from" @@ -101,6 +101,8 @@ async function resetLastPlayed() { color: "green", timeout: 2000, }); + + romsStore.removeFromContinuePlaying(props.rom); }) .catch((error) => { console.log(error); diff --git a/frontend/src/services/api/rom.ts b/frontend/src/services/api/rom.ts index 4347a755b..49c5594ba 100644 --- a/frontend/src/services/api/rom.ts +++ b/frontend/src/services/api/rom.ts @@ -187,7 +187,7 @@ async function updateUserRomProps({ romId: number; data: Partial; updateLastPlayed?: boolean; -}): Promise<{ data: DetailedRom }> { +}): Promise<{ data: RomUserSchema }> { return api.put(`/roms/${romId}/props`, { data: data, update_last_played: updateLastPlayed, diff --git a/frontend/src/stores/roms.ts b/frontend/src/stores/roms.ts index 305d1c4ea..be3168fd3 100644 --- a/frontend/src/stores/roms.ts +++ b/frontend/src/stores/roms.ts @@ -101,9 +101,17 @@ export default defineStore("roms", { addToRecent(rom: SimpleRom) { this.recentRoms = [rom, ...this.recentRoms]; }, + removeFromRecent(rom: SimpleRom) { + this.recentRoms = this.recentRoms.filter((value) => value.id !== rom.id); + }, addToContinuePlaying(rom: SimpleRom) { this.continuePlayingRoms = [rom, ...this.continuePlayingRoms]; }, + removeFromContinuePlaying(rom: SimpleRom) { + this.continuePlayingRoms = this.continuePlayingRoms.filter( + (value) => value.id !== rom.id, + ); + }, update(rom: SimpleRom) { this.allRoms = this.allRoms.map((value) => value.id === rom.id ? rom : value, From 56631db6369871ade6bc958fcab58a2eb9c7211b Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Mon, 13 Jan 2025 09:43:22 -0300 Subject: [PATCH 07/44] fix: Poetry dependency installation during pytest job With Poetry 2.0, we now need to explicitly tell it to install the `test` extra. --- .github/workflows/pytest.yml | 2 +- DEVELOPER_SETUP.md | 4 ++-- pyproject.toml | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 30b80beab..f9deec872 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -50,7 +50,7 @@ jobs: - name: Install dependencies run: | - poetry install --sync + poetry sync --extras test - name: Initiate database run: | diff --git a/DEVELOPER_SETUP.md b/DEVELOPER_SETUP.md index fec76bc6d..9c4c900f1 100644 --- a/DEVELOPER_SETUP.md +++ b/DEVELOPER_SETUP.md @@ -45,14 +45,14 @@ Then create the virtual environment ```sh # Fix disable parallel installation stuck: $> poetry config experimental.new-installer false # Fix Loading macOS/linux stuck: $> export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring -poetry install --sync +poetry sync ``` If you are on Arch Linux or another Arch-based distro, you need to run the command as follows: ```sh # https://bbs.archlinux.org/viewtopic.php?id=296542 -CFLAGS="-Wno-error=incompatible-pointer-types" poetry install --sync +CFLAGS="-Wno-error=incompatible-pointer-types" poetry sync ``` #### - Spin up mariadb in docker diff --git a/pyproject.toml b/pyproject.toml index cbb35242c..c22fb39c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,3 +70,4 @@ Source = "https://github.com/rommapp/romm" [tool.poetry] package-mode = false +requires-poetry = ">=2.0" From c164a9a32ecc55113e590aeb10983c237a793ec1 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Mon, 13 Jan 2025 10:02:32 -0500 Subject: [PATCH 08/44] [ROMM-1273] Use platform versions to check if game playable --- frontend/src/components/Details/ActionBar.vue | 22 ++++++++++++++----- .../components/common/Game/Card/ActionBar.vue | 17 +++++++++----- frontend/src/components/common/Game/Table.vue | 15 +++++++++++-- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/Details/ActionBar.vue b/frontend/src/components/Details/ActionBar.vue index b93a3ee75..77acd3a87 100644 --- a/frontend/src/components/Details/ActionBar.vue +++ b/frontend/src/components/Details/ActionBar.vue @@ -4,6 +4,7 @@ import CopyRomDownloadLinkDialog from "@/components/common/Game/Dialog/CopyDownl import romApi from "@/services/api/rom"; import storeDownload from "@/stores/download"; import storeHeartbeat from "@/stores/heartbeat"; +import storeConfig from "@/stores/config"; import type { DetailedRom } from "@/stores/roms"; import type { Events } from "@/types/emitter"; import { @@ -14,6 +15,7 @@ import { } from "@/utils"; import type { Emitter } from "mitt"; import { computed, inject, ref } from "vue"; +import { storeToRefs } from "pinia"; // Props const props = defineProps<{ rom: DetailedRom }>(); @@ -22,13 +24,23 @@ const heartbeatStore = storeHeartbeat(); const emitter = inject>("emitter"); const playInfoIcon = ref("mdi-play"); const qrCodeIcon = ref("mdi-qrcode"); +const configStore = storeConfig(); +const { config } = storeToRefs(configStore); -const ejsEmulationSupported = computed(() => - isEJSEmulationSupported(props.rom.platform_slug, heartbeatStore.value), -); -const ruffleEmulationSupported = computed(() => - isRuffleEmulationSupported(props.rom.platform_slug, heartbeatStore.value), +const platformSlug = computed(() => + props.rom.platform_slug in config.value.PLATFORMS_VERSIONS + ? config.value.PLATFORMS_VERSIONS[props.rom.platform_slug] + : props.rom.platform_slug, ); + +const ejsEmulationSupported = computed(() => { + return isEJSEmulationSupported(platformSlug.value, heartbeatStore.value); +}); + +const ruffleEmulationSupported = computed(() => { + return isRuffleEmulationSupported(platformSlug.value, heartbeatStore.value); +}); + const is3DSRom = computed(() => { return is3DSCIARom(props.rom); }); diff --git a/frontend/src/components/common/Game/Card/ActionBar.vue b/frontend/src/components/common/Game/Card/ActionBar.vue index 06ef3df5b..9cfd03bf5 100644 --- a/frontend/src/components/common/Game/Card/ActionBar.vue +++ b/frontend/src/components/common/Game/Card/ActionBar.vue @@ -3,6 +3,7 @@ import AdminMenu from "@/components/common/Game/AdminMenu.vue"; import romApi from "@/services/api/rom"; import storeDownload from "@/stores/download"; import storeHeartbeat from "@/stores/heartbeat"; +import storeConfig from "@/stores/config"; import type { SimpleRom } from "@/stores/roms"; import type { Events } from "@/types/emitter"; import { @@ -12,22 +13,28 @@ import { } from "@/utils"; import type { Emitter } from "mitt"; import { computed, inject } from "vue"; +import { storeToRefs } from "pinia"; // Props const props = defineProps<{ rom: SimpleRom }>(); const downloadStore = storeDownload(); const heartbeatStore = storeHeartbeat(); const emitter = inject>("emitter"); +const configStore = storeConfig(); +const { config } = storeToRefs(configStore); + +const platformSlug = computed(() => { + return props.rom.platform_slug in config.value.PLATFORMS_VERSIONS + ? config.value.PLATFORMS_VERSIONS[props.rom.platform_slug] + : props.rom.platform_slug; +}); const ejsEmulationSupported = computed(() => { - return isEJSEmulationSupported(props.rom.platform_slug, heartbeatStore.value); + return isEJSEmulationSupported(platformSlug.value, heartbeatStore.value); }); const ruffleEmulationSupported = computed(() => { - return isRuffleEmulationSupported( - props.rom.platform_slug, - heartbeatStore.value, - ); + return isRuffleEmulationSupported(platformSlug.value, heartbeatStore.value); }); const is3DSRom = computed(() => { diff --git a/frontend/src/components/common/Game/Table.vue b/frontend/src/components/common/Game/Table.vue index a7659d247..56fbc2d60 100644 --- a/frontend/src/components/common/Game/Table.vue +++ b/frontend/src/components/common/Game/Table.vue @@ -5,6 +5,7 @@ import RAvatarRom from "@/components/common/Game/RAvatar.vue"; import romApi from "@/services/api/rom"; import storeDownload from "@/stores/download"; import storeRoms, { type SimpleRom } from "@/stores/roms"; +import storeConfig from "@/stores/config"; import storeHeartbeat from "@/stores/heartbeat"; import type { Events } from "@/types/emitter"; import { @@ -34,6 +35,8 @@ const downloadStore = storeDownload(); const romsStore = storeRoms(); const { filteredRoms, selectedRoms } = storeToRefs(romsStore); const heartbeatStore = storeHeartbeat(); +const configStore = storeConfig(); +const { config } = storeToRefs(configStore); const page = ref(parseInt(window.location.hash.slice(1)) || 1); const storedRomsPerPage = parseInt(localStorage.getItem("romsPerPage") ?? ""); const itemsPerPage = ref(isNaN(storedRomsPerPage) ? 25 : storedRomsPerPage); @@ -106,12 +109,20 @@ function updateUrlHash() { window.location.hash = String(page.value); } +function getTruePlatformSlug(platformSlug: string) { + return platformSlug in config.value.PLATFORMS_VERSIONS + ? config.value.PLATFORMS_VERSIONS[platformSlug] + : platformSlug; +} + function checkIfEJSEmulationSupported(platformSlug: string) { - return isEJSEmulationSupported(platformSlug, heartbeatStore.value); + const slug = getTruePlatformSlug(platformSlug); + return isEJSEmulationSupported(slug, heartbeatStore.value); } function checkIfRuffleEmulationSupported(platformSlug: string) { - return isRuffleEmulationSupported(platformSlug, heartbeatStore.value); + const slug = getTruePlatformSlug(platformSlug); + return isRuffleEmulationSupported(slug, heartbeatStore.value); } function updateSelectAll() { From 68674864196d57488347f024fabedb25c5db476e Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Mon, 13 Jan 2025 22:42:45 -0300 Subject: [PATCH 09/44] fix: Set users email to null if empty When a user does not set an email address, we now set it to `NULL` in the database. That bypasses the unique constraint on the `email` column, allowing multiple users to have no email address set. Fixes #1445. --- .../alembic/versions/0030_user_email_null.py | 25 +++++++++++++++++++ backend/endpoints/user.py | 23 ++++++++--------- 2 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 backend/alembic/versions/0030_user_email_null.py diff --git a/backend/alembic/versions/0030_user_email_null.py b/backend/alembic/versions/0030_user_email_null.py new file mode 100644 index 000000000..14d6ba41f --- /dev/null +++ b/backend/alembic/versions/0030_user_email_null.py @@ -0,0 +1,25 @@ +"""Change empty string in users.email to NULL. + +Revision ID: 951473b0c581 +Revises: 0029_platforms_custom_name +Create Date: 2025-01-14 01:30:39.696257 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "0030_user_email_null" +down_revision = "0029_platforms_custom_name" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + with op.batch_alter_table("users", schema=None) as batch_op: + batch_op.execute("UPDATE users SET email = NULL WHERE email = ''") + + +def downgrade() -> None: + with op.batch_alter_table("users", schema=None) as batch_op: + batch_op.execute("UPDATE users SET email = '' WHERE email IS NULL") diff --git a/backend/endpoints/user.py b/backend/endpoints/user.py index dbe24538e..bb341b279 100644 --- a/backend/endpoints/user.py +++ b/backend/endpoints/user.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Annotated +from typing import Annotated, Any from anyio import open_file from config import ASSETS_BASE_PATH @@ -51,8 +51,7 @@ def add_user( detail="Forbidden", ) - existing_user_by_username = db_user_handler.get_user_by_username(username.lower()) - if existing_user_by_username: + if db_user_handler.get_user_by_username(username.lower()): msg = f"Username {username.lower()} already exists" log.error(msg) raise HTTPException( @@ -60,9 +59,8 @@ def add_user( detail=msg, ) - existing_user_by_email = db_user_handler.get_user_by_email(email.lower()) - if existing_user_by_email: - msg = f"Uesr with email {email.lower()} already exists" + if email and db_user_handler.get_user_by_email(email.lower()): + msg = f"User with email {email.lower()} already exists" log.error(msg) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -72,7 +70,7 @@ def add_user( user = User( username=username.lower(), hashed_password=auth_handler.get_password_hash(password), - email=email.lower(), + email=email.lower() or None, role=Role[role.upper()], ) @@ -154,7 +152,7 @@ async def update_user( if db_user.id != request.user.id and request.user.role != Role.ADMIN: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") - cleaned_data = {} + cleaned_data: dict[str, Any] = {} if form_data.username and form_data.username != db_user.username: existing_user = db_user_handler.get_user_by_username(form_data.username.lower()) @@ -173,9 +171,10 @@ async def update_user( form_data.password ) - if form_data.email and form_data.email != db_user.email: - existing_user = db_user_handler.get_user_by_email(form_data.email.lower()) - if existing_user: + if form_data.email is not None and form_data.email != db_user.email: + if form_data.email and db_user_handler.get_user_by_email( + form_data.email.lower() + ): msg = f"User with email {form_data.email} already exists" log.error(msg) raise HTTPException( @@ -183,7 +182,7 @@ async def update_user( detail=msg, ) - cleaned_data["email"] = form_data.email.lower() + cleaned_data["email"] = form_data.email.lower() or None # You can't change your own role if form_data.role and request.user.id != id: From 9319e3dfe45ea568327b56e76b14d3c477a99189 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Mon, 13 Jan 2025 20:46:22 -0500 Subject: [PATCH 10/44] [ROMM-1484] Manually set control scheme for sega games --- backend/endpoints/rom.py | 12 ++++++ frontend/src/utils/index.ts | 37 ++++++++++++++++++- .../src/views/Player/EmulatorJS/Player.vue | 10 ++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/backend/endpoints/rom.py b/backend/endpoints/rom.py index ce2e54403..12dba2e7b 100644 --- a/backend/endpoints/rom.py +++ b/backend/endpoints/rom.py @@ -283,6 +283,18 @@ async def get_rom_content( log.info(f"User {current_username} is downloading {rom.file_name}") if not rom.multi: + # Serve the file directly in development mode for emulatorjs + if DEV_MODE: + return FileResponse( + path=rom_path, + filename=rom.file_name, + headers={ + "Content-Disposition": f'attachment; filename="{quote(rom.file_name)}"', + "Content-Type": "application/octet-stream", + "Content-Length": str(rom.file_size_bytes), + }, + ) + return FileRedirectResponse( download_path=Path(f"/library/{rom.full_path}"), filename=rom.file_name, diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 9c26d0ccc..eb3bc6d45 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -363,7 +363,7 @@ const _EJS_CORES_MAP = { ps: ["pcsx_rearmed", "mednafen_psx_hw"], psp: ["ppsspp"], segacd: ["genesis_plus_gx", "picodrive"], - // sega32: ["picodrive"], // Broken: https://github.com/EmulatorJS/EmulatorJS/issues/579 + sega32: ["picodrive"], gamegear: ["genesis_plus_gx"], sms: ["genesis_plus_gx"], "sega-mark-iii": ["genesis_plus_gx"], @@ -453,6 +453,41 @@ export function isEJSThreadsSupported(): boolean { return typeof SharedArrayBuffer !== "undefined"; } +// This is a workaround to set the control scheme for Sega systems using the same cores +const _EJS_CONTROL_SCHEMES = { + segacd: "segaCD", + sega32: "sega32x", + gamegear: "segaGG", + sms: "segaMS", + "sega-mark-iii": "segaMS", + "sega-master-system-ii": "segaMS", + "master-system-super-compact": "segaMS", + "master-system-girl": "segaMS", + "genesis-slash-megadrive": "segaMD", + "sega-mega-drive-2-slash-genesis": "segaMD", + "sega-mega-jet": "segaMD", + "mega-pc": "segaMD", + "tera-drive": "segaMD", + "sega-nomad": "segaMD", + saturn: "segaSaturn", +}; + +type EJSControlSlug = keyof typeof _EJS_CONTROL_SCHEMES; + +/** + * Get the control scheme for a given platform. + * + * @param platformSlug The platform slug. + * @returns The control scheme. + */ +export function getControlSchemeForPlatform( + platformSlug: string, +): string | null { + return platformSlug in _EJS_CONTROL_SCHEMES + ? _EJS_CONTROL_SCHEMES[platformSlug as EJSControlSlug] + : null; +} + /** * Check if Ruffle emulation is supported for a given platform. * diff --git a/frontend/src/views/Player/EmulatorJS/Player.vue b/frontend/src/views/Player/EmulatorJS/Player.vue index d4d1b4802..7c25a308a 100644 --- a/frontend/src/views/Player/EmulatorJS/Player.vue +++ b/frontend/src/views/Player/EmulatorJS/Player.vue @@ -4,7 +4,11 @@ import saveApi, { saveApi as api } from "@/services/api/save"; import screenshotApi from "@/services/api/screenshot"; import stateApi from "@/services/api/state"; import type { DetailedRom } from "@/stores/roms"; -import { areThreadsRequiredForEJSCore, getSupportedEJSCores } from "@/utils"; +import { + areThreadsRequiredForEJSCore, + getSupportedEJSCores, + getControlSchemeForPlatform, +} from "@/utils"; import { onBeforeUnmount, onMounted, ref } from "vue"; const props = defineProps<{ @@ -39,6 +43,7 @@ declare global { EJS_startOnLoaded: boolean; EJS_fullscreenOnLoaded: boolean; EJS_threads: boolean; + EJS_controlScheme: string | null; // eslint-disable-next-line @typescript-eslint/no-explicit-any EJS_emulator: any; EJS_onGameStart: () => void; @@ -52,6 +57,9 @@ declare global { const supportedCores = getSupportedEJSCores(romRef.value.platform_slug); window.EJS_core = supportedCores.find((core) => core === props.core) ?? supportedCores[0]; +window.EJS_controlScheme = getControlSchemeForPlatform( + romRef.value.platform_slug, +); window.EJS_threads = areThreadsRequiredForEJSCore(window.EJS_core); window.EJS_gameID = romRef.value.id; window.EJS_gameUrl = `/api/roms/${romRef.value.id}/content/${romRef.value.file_name}`; From 195b0cf742f6db20e89e75e7f219e1c4f9de46ce Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Mon, 13 Jan 2025 21:50:18 -0500 Subject: [PATCH 11/44] [HOTFIX] Change all DateTime columns to TIMESTAMP --- .../versions/0031_datetime_to_timestamp.py | 359 ++++++++++++++++++ backend/endpoints/rom.py | 8 +- backend/models/base.py | 6 +- backend/models/rom.py | 4 +- backend/models/user.py | 6 +- 5 files changed, 373 insertions(+), 10 deletions(-) create mode 100644 backend/alembic/versions/0031_datetime_to_timestamp.py diff --git a/backend/alembic/versions/0031_datetime_to_timestamp.py b/backend/alembic/versions/0031_datetime_to_timestamp.py new file mode 100644 index 000000000..ce89a695e --- /dev/null +++ b/backend/alembic/versions/0031_datetime_to_timestamp.py @@ -0,0 +1,359 @@ +"""empty message + +Revision ID: 0031_datetime_to_timestamp +Revises: 0030_user_email_null +Create Date: 2025-01-14 04:13:33.209508 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = "0031_datetime_to_timestamp" +down_revision = "0030_user_email_null" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("collections", schema=None) as batch_op: + batch_op.alter_column( + "created_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + batch_op.alter_column( + "updated_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + with op.batch_alter_table("firmware", schema=None) as batch_op: + batch_op.alter_column( + "created_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + batch_op.alter_column( + "updated_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + with op.batch_alter_table("platforms", schema=None) as batch_op: + batch_op.alter_column( + "created_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + batch_op.alter_column( + "updated_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + with op.batch_alter_table("rom_user", schema=None) as batch_op: + batch_op.alter_column( + "last_played", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=True, + ) + batch_op.alter_column( + "created_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + batch_op.alter_column( + "updated_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + with op.batch_alter_table("roms", schema=None) as batch_op: + batch_op.alter_column( + "file_name", existing_type=mysql.VARCHAR(length=450), nullable=False + ) + batch_op.alter_column( + "file_name_no_tags", existing_type=mysql.VARCHAR(length=450), nullable=False + ) + batch_op.alter_column( + "file_name_no_ext", existing_type=mysql.VARCHAR(length=450), nullable=False + ) + batch_op.alter_column( + "file_extension", existing_type=mysql.VARCHAR(length=100), nullable=False + ) + batch_op.alter_column( + "file_path", existing_type=mysql.VARCHAR(length=1000), nullable=False + ) + batch_op.alter_column( + "created_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + batch_op.alter_column( + "updated_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + with op.batch_alter_table("saves", schema=None) as batch_op: + batch_op.alter_column( + "created_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + batch_op.alter_column( + "updated_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + with op.batch_alter_table("screenshots", schema=None) as batch_op: + batch_op.alter_column( + "created_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + batch_op.alter_column( + "updated_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + with op.batch_alter_table("states", schema=None) as batch_op: + batch_op.alter_column( + "created_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + batch_op.alter_column( + "updated_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + with op.batch_alter_table("users", schema=None) as batch_op: + batch_op.alter_column( + "role", + existing_type=mysql.ENUM("VIEWER", "EDITOR", "ADMIN"), + nullable=False, + ) + batch_op.alter_column( + "avatar_path", existing_type=mysql.VARCHAR(length=255), nullable=False + ) + batch_op.alter_column( + "last_login", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=True, + ) + batch_op.alter_column( + "last_active", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=True, + ) + batch_op.alter_column( + "created_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + batch_op.alter_column( + "updated_at", + existing_type=mysql.DATETIME(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("users", schema=None) as batch_op: + batch_op.alter_column( + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "last_active", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=True, + ) + batch_op.alter_column( + "last_login", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=True, + ) + batch_op.alter_column( + "avatar_path", existing_type=mysql.VARCHAR(length=255), nullable=True + ) + batch_op.alter_column( + "role", existing_type=mysql.ENUM("VIEWER", "EDITOR", "ADMIN"), nullable=True + ) + + with op.batch_alter_table("states", schema=None) as batch_op: + batch_op.alter_column( + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + + with op.batch_alter_table("screenshots", schema=None) as batch_op: + batch_op.alter_column( + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + + with op.batch_alter_table("saves", schema=None) as batch_op: + batch_op.alter_column( + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + + with op.batch_alter_table("roms", schema=None) as batch_op: + batch_op.alter_column( + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "file_path", existing_type=mysql.VARCHAR(length=1000), nullable=True + ) + batch_op.alter_column( + "file_extension", existing_type=mysql.VARCHAR(length=100), nullable=True + ) + batch_op.alter_column( + "file_name_no_ext", existing_type=mysql.VARCHAR(length=450), nullable=True + ) + batch_op.alter_column( + "file_name_no_tags", existing_type=mysql.VARCHAR(length=450), nullable=True + ) + batch_op.alter_column( + "file_name", existing_type=mysql.VARCHAR(length=450), nullable=True + ) + + with op.batch_alter_table("rom_user", schema=None) as batch_op: + batch_op.alter_column( + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "last_played", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=True, + ) + + with op.batch_alter_table("platforms", schema=None) as batch_op: + batch_op.alter_column( + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + + with op.batch_alter_table("firmware", schema=None) as batch_op: + batch_op.alter_column( + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + + with op.batch_alter_table("collections", schema=None) as batch_op: + batch_op.alter_column( + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + batch_op.alter_column( + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=mysql.DATETIME(), + existing_nullable=False, + ) + + # ### end Alembic commands ### diff --git a/backend/endpoints/rom.py b/backend/endpoints/rom.py index 12dba2e7b..aea2c956b 100644 --- a/backend/endpoints/rom.py +++ b/backend/endpoints/rom.py @@ -570,7 +570,7 @@ async def delete_roms( @protected_route(router.put, "/roms/{id}/props", [Scope.ROMS_USER_WRITE]) async def update_rom_user(request: Request, id: int) -> RomUserSchema: data = await request.json() - data = data.get("data", {}) + rom_user_data = data.get("data", {}) rom = db_rom_handler.get_rom(id) @@ -595,7 +595,11 @@ async def update_rom_user(request: Request, id: int) -> RomUserSchema: "last_played", ] - cleaned_data = {field: data[field] for field in fields_to_update if field in data} + cleaned_data = { + field: rom_user_data[field] + for field in fields_to_update + if field in rom_user_data + } if data.get("update_last_played", False): cleaned_data.update({"last_played": datetime.now(timezone.utc)}) diff --git a/backend/models/base.py b/backend/models/base.py index 286af85cf..d606a69f5 100644 --- a/backend/models/base.py +++ b/backend/models/base.py @@ -1,13 +1,13 @@ from datetime import datetime -from sqlalchemy import DateTime, func +from sqlalchemy import TIMESTAMP, func from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column class BaseModel(DeclarativeBase): created_at: Mapped[datetime] = mapped_column( - DateTime(timezone=True), server_default=func.now() + TIMESTAMP(timezone=True), server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( - DateTime(timezone=True), server_default=func.now(), onupdate=func.now() + TIMESTAMP(timezone=True), server_default=func.now(), onupdate=func.now() ) diff --git a/backend/models/rom.py b/backend/models/rom.py index ddf45a801..0d7d12a3b 100644 --- a/backend/models/rom.py +++ b/backend/models/rom.py @@ -8,8 +8,8 @@ from typing import TYPE_CHECKING, Any, TypedDict from config import FRONTEND_RESOURCES_PATH from models.base import BaseModel from sqlalchemy import ( + TIMESTAMP, BigInteger, - DateTime, Enum, ForeignKey, Index, @@ -254,7 +254,7 @@ class RomUser(BaseModel): note_is_public: Mapped[bool] = mapped_column(default=False) is_main_sibling: Mapped[bool] = mapped_column(default=False) - last_played: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) + last_played: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True)) backlogged: Mapped[bool] = mapped_column(default=False) now_playing: Mapped[bool] = mapped_column(default=False) diff --git a/backend/models/user.py b/backend/models/user.py index 34046e862..da1a1784b 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING from handler.auth.constants import DEFAULT_SCOPES, FULL_SCOPES, WRITE_SCOPES, Scope from models.base import BaseModel -from sqlalchemy import DateTime, Enum, String +from sqlalchemy import TIMESTAMP, Enum, String from sqlalchemy.orm import Mapped, mapped_column, relationship from starlette.authentication import SimpleUser @@ -36,8 +36,8 @@ class User(BaseModel, SimpleUser): enabled: Mapped[bool] = mapped_column(default=True) role: Mapped[Role] = mapped_column(Enum(Role), default=Role.VIEWER) avatar_path: Mapped[str] = mapped_column(String(length=255), default="") - last_login: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) - last_active: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) + last_login: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True)) + last_active: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True)) saves: Mapped[list[Save]] = relationship(back_populates="user") states: Mapped[list[State]] = relationship(back_populates="user") From fefcc63fd1b9325e6aa7d8823d48ba25dca216b9 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Mon, 13 Jan 2025 22:10:04 -0500 Subject: [PATCH 12/44] fix server detault value --- .../versions/0031_datetime_to_timestamp.py | 80 +++++++++---------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/backend/alembic/versions/0031_datetime_to_timestamp.py b/backend/alembic/versions/0031_datetime_to_timestamp.py index ce89a695e..77074246d 100644 --- a/backend/alembic/versions/0031_datetime_to_timestamp.py +++ b/backend/alembic/versions/0031_datetime_to_timestamp.py @@ -25,12 +25,14 @@ def upgrade() -> None: existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "updated_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("firmware", schema=None) as batch_op: @@ -39,12 +41,14 @@ def upgrade() -> None: existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "updated_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("platforms", schema=None) as batch_op: @@ -53,12 +57,14 @@ def upgrade() -> None: existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "updated_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("rom_user", schema=None) as batch_op: @@ -73,41 +79,30 @@ def upgrade() -> None: existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "updated_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("roms", schema=None) as batch_op: - batch_op.alter_column( - "file_name", existing_type=mysql.VARCHAR(length=450), nullable=False - ) - batch_op.alter_column( - "file_name_no_tags", existing_type=mysql.VARCHAR(length=450), nullable=False - ) - batch_op.alter_column( - "file_name_no_ext", existing_type=mysql.VARCHAR(length=450), nullable=False - ) - batch_op.alter_column( - "file_extension", existing_type=mysql.VARCHAR(length=100), nullable=False - ) - batch_op.alter_column( - "file_path", existing_type=mysql.VARCHAR(length=1000), nullable=False - ) batch_op.alter_column( "created_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "updated_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("saves", schema=None) as batch_op: @@ -116,12 +111,14 @@ def upgrade() -> None: existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "updated_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("screenshots", schema=None) as batch_op: @@ -130,12 +127,14 @@ def upgrade() -> None: existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "updated_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("states", schema=None) as batch_op: @@ -144,23 +143,17 @@ def upgrade() -> None: existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "updated_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("users", schema=None) as batch_op: - batch_op.alter_column( - "role", - existing_type=mysql.ENUM("VIEWER", "EDITOR", "ADMIN"), - nullable=False, - ) - batch_op.alter_column( - "avatar_path", existing_type=mysql.VARCHAR(length=255), nullable=False - ) batch_op.alter_column( "last_login", existing_type=mysql.DATETIME(), @@ -178,12 +171,14 @@ def upgrade() -> None: existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "updated_at", existing_type=mysql.DATETIME(), type_=sa.TIMESTAMP(timezone=True), existing_nullable=False, + existing_server_default=sa.text("now()"), ) # ### end Alembic commands ### @@ -197,12 +192,14 @@ def downgrade() -> None: existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "created_at", existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "last_active", @@ -216,12 +213,6 @@ def downgrade() -> None: type_=mysql.DATETIME(), existing_nullable=True, ) - batch_op.alter_column( - "avatar_path", existing_type=mysql.VARCHAR(length=255), nullable=True - ) - batch_op.alter_column( - "role", existing_type=mysql.ENUM("VIEWER", "EDITOR", "ADMIN"), nullable=True - ) with op.batch_alter_table("states", schema=None) as batch_op: batch_op.alter_column( @@ -229,12 +220,14 @@ def downgrade() -> None: existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "created_at", existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("screenshots", schema=None) as batch_op: @@ -243,12 +236,14 @@ def downgrade() -> None: existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "created_at", existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("saves", schema=None) as batch_op: @@ -257,12 +252,14 @@ def downgrade() -> None: existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "created_at", existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("roms", schema=None) as batch_op: @@ -271,27 +268,14 @@ def downgrade() -> None: existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "created_at", existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, - ) - batch_op.alter_column( - "file_path", existing_type=mysql.VARCHAR(length=1000), nullable=True - ) - batch_op.alter_column( - "file_extension", existing_type=mysql.VARCHAR(length=100), nullable=True - ) - batch_op.alter_column( - "file_name_no_ext", existing_type=mysql.VARCHAR(length=450), nullable=True - ) - batch_op.alter_column( - "file_name_no_tags", existing_type=mysql.VARCHAR(length=450), nullable=True - ) - batch_op.alter_column( - "file_name", existing_type=mysql.VARCHAR(length=450), nullable=True + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("rom_user", schema=None) as batch_op: @@ -300,12 +284,14 @@ def downgrade() -> None: existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "created_at", existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "last_played", @@ -320,12 +306,14 @@ def downgrade() -> None: existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "created_at", existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("firmware", schema=None) as batch_op: @@ -334,12 +322,14 @@ def downgrade() -> None: existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "created_at", existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) with op.batch_alter_table("collections", schema=None) as batch_op: @@ -348,12 +338,14 @@ def downgrade() -> None: existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) batch_op.alter_column( "created_at", existing_type=sa.TIMESTAMP(timezone=True), type_=mysql.DATETIME(), existing_nullable=False, + existing_server_default=sa.text("now()"), ) # ### end Alembic commands ### From 5ee3f631eb8de5ead066c39aba6dea866fa331e1 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Thu, 16 Jan 2025 14:48:41 -0500 Subject: [PATCH 13/44] [ROMM-1495] Fix filters in filter bar --- .../Gallery/AppBar/Search/SearchTextField.vue | 65 +++++++++---------- frontend/src/views/Gallery/Collection.vue | 61 ++++++++--------- frontend/src/views/Gallery/Platform.vue | 58 ++++++++--------- 3 files changed, 92 insertions(+), 92 deletions(-) diff --git a/frontend/src/components/Gallery/AppBar/Search/SearchTextField.vue b/frontend/src/components/Gallery/AppBar/Search/SearchTextField.vue index 738c2b3e9..18648bcb5 100644 --- a/frontend/src/components/Gallery/AppBar/Search/SearchTextField.vue +++ b/frontend/src/components/Gallery/AppBar/Search/SearchTextField.vue @@ -61,45 +61,44 @@ function setFilters() { ]); } -function fetchRoms() { +async function fetchRoms() { if (searchText.value) { // Auto hide android keyboard const inputElement = document.getElementById("search-text-field"); inputElement?.blur(); gettingRoms.value = true; - romApi - .getRoms({ searchTerm: searchText.value }) - .then(({ data }) => { - data = data.sort((a, b) => { - return a.platform_name.localeCompare(b.platform_name); - }); - romsStore.set(data); - romsStore.setFiltered(data, galleryFilterStore); - }) - .catch((error) => { - emitter?.emit("snackbarShow", { - msg: `Couldn't fetch roms: ${error}`, - icon: "mdi-close-circle", - color: "red", - timeout: 4000, - }); - console.error(`Couldn't fetch roms: ${error}`); - }) - .finally(() => { - gettingRoms.value = false; + + try { + const { data } = await romApi.getRoms({ searchTerm: searchText.value }); + const sortedData = data.sort((a, b) => { + return a.platform_name.localeCompare(b.platform_name); }); - galleryFilterStore.setFilterPlatforms([ - ...new Map( - romsStore.filteredRoms.map((rom) => { - const platform = allPlatforms.value.find( - (p) => p.id === rom.platform_id, - ); - return [rom.platform_name, platform]; - }), - ).values(), - ] as Platform[]); - setFilters(); - galleryFilterStore.activeFilterDrawer = false; + romsStore.set(sortedData); + romsStore.setFiltered(sortedData, galleryFilterStore); + } catch (error) { + emitter?.emit("snackbarShow", { + msg: `Couldn't fetch roms: ${error}`, + icon: "mdi-close-circle", + color: "red", + timeout: 4000, + }); + console.error(`Couldn't fetch roms: ${error}`); + } finally { + gettingRoms.value = false; + + galleryFilterStore.setFilterPlatforms([ + ...new Map( + romsStore.filteredRoms.map((rom) => { + const platform = allPlatforms.value.find( + (p) => p.id === rom.platform_id, + ); + return [rom.platform_name, platform]; + }), + ).values(), + ] as Platform[]); + setFilters(); + galleryFilterStore.activeFilterDrawer = false; + } } } diff --git a/frontend/src/views/Gallery/Collection.vue b/frontend/src/views/Gallery/Collection.vue index 860a4242f..d031f97a3 100644 --- a/frontend/src/views/Gallery/Collection.vue +++ b/frontend/src/views/Gallery/Collection.vue @@ -53,34 +53,37 @@ async function fetchRoms() { scrim: false, }); - await romApi - .getRoms({ + try { + const { data } = await romApi.getRoms({ collectionId: romsStore.currentCollection?.id, searchTerm: normalizeString(galleryFilterStore.filterText), - }) - .then(({ data }) => { - romsStore.set(data); - romsStore.setFiltered(data, galleryFilterStore); - }) - .catch((error) => { - emitter?.emit("snackbarShow", { - msg: `Couldn't fetch roms for collection ID ${currentCollection.value?.id}: ${error}`, - icon: "mdi-close-circle", - color: "red", - timeout: 4000, - }); - console.error( - `Couldn't fetch roms for collection ID ${currentCollection.value?.id}: ${error}`, - ); - noCollectionError.value = true; - }) - .finally(() => { - gettingRoms.value = false; - emitter?.emit("showLoadingDialog", { - loading: gettingRoms.value, - scrim: false, - }); }); + romsStore.set(data); + romsStore.setFiltered(data, galleryFilterStore); + + gettingRoms.value = false; + emitter?.emit("showLoadingDialog", { + loading: gettingRoms.value, + scrim: false, + }); + } catch (error) { + emitter?.emit("snackbarShow", { + msg: `Couldn't fetch roms for collection ID ${currentCollection.value?.id}: ${error}`, + icon: "mdi-close-circle", + color: "red", + timeout: 4000, + }); + console.error( + `Couldn't fetch roms for collection ID ${currentCollection.value?.id}: ${error}`, + ); + noCollectionError.value = true; + } finally { + gettingRoms.value = false; + emitter?.emit("showLoadingDialog", { + loading: gettingRoms.value, + scrim: false, + }); + } } function setFilters() { @@ -216,7 +219,7 @@ onMounted(async () => { watch( () => allCollections.value, - (collections) => { + async (collections) => { if ( collections.length > 0 && collections.some((collection) => collection.id === routeCollectionId) @@ -233,7 +236,7 @@ onMounted(async () => { ) { romsStore.setCurrentCollection(collection); resetGallery(); - fetchRoms(); + await fetchRoms(); setFilters(); } @@ -256,7 +259,7 @@ onBeforeRouteUpdate(async (to, from) => { watch( () => allCollections.value, - (collections) => { + async (collections) => { if (collections.length > 0) { const collection = collections.find( (collection) => collection.id === routeCollectionId, @@ -269,7 +272,7 @@ onBeforeRouteUpdate(async (to, from) => { collection ) { romsStore.setCurrentCollection(collection); - fetchRoms(); + await fetchRoms(); setFilters(); } } diff --git a/frontend/src/views/Gallery/Platform.vue b/frontend/src/views/Gallery/Platform.vue index 62d8ada98..49a73c59e 100644 --- a/frontend/src/views/Gallery/Platform.vue +++ b/frontend/src/views/Gallery/Platform.vue @@ -44,7 +44,7 @@ const emitter = inject>("emitter"); emitter?.on("filter", onFilterChange); // Functions -function fetchRoms() { +async function fetchRoms() { if (gettingRoms.value) return; gettingRoms.value = true; @@ -53,34 +53,32 @@ function fetchRoms() { scrim: false, }); - romApi - .getRoms({ + try { + const { data } = await romApi.getRoms({ platformId: romsStore.currentPlatform?.id, searchTerm: normalizeString(galleryFilterStore.filterText), - }) - .then(({ data }) => { - romsStore.set(data); - romsStore.setFiltered(data, galleryFilterStore); - }) - .catch((error) => { - emitter?.emit("snackbarShow", { - msg: `Couldn't fetch roms for platform ID ${currentPlatform.value?.id}: ${error}`, - icon: "mdi-close-circle", - color: "red", - timeout: 4000, - }); - console.error( - `Couldn't fetch roms for platform ID ${currentPlatform.value?.id}: ${error}`, - ); - noPlatformError.value = true; - }) - .finally(() => { - gettingRoms.value = false; - emitter?.emit("showLoadingDialog", { - loading: gettingRoms.value, - scrim: false, - }); }); + + romsStore.set(data); + romsStore.setFiltered(data, galleryFilterStore); + } catch (error) { + emitter?.emit("snackbarShow", { + msg: `Couldn't fetch roms for platform ID ${currentPlatform.value?.id}: ${error}`, + icon: "mdi-close-circle", + color: "red", + timeout: 4000, + }); + console.error( + `Couldn't fetch roms for platform ID ${currentPlatform.value?.id}: ${error}`, + ); + noPlatformError.value = true; + } finally { + gettingRoms.value = false; + emitter?.emit("showLoadingDialog", { + loading: gettingRoms.value, + scrim: false, + }); + } } function setFilters() { @@ -225,7 +223,7 @@ onMounted(async () => { watch( () => allPlatforms.value, - (platforms) => { + async (platforms) => { if (platforms.length > 0) { if (platforms.some((platform) => platform.id === routePlatformId)) { const platform = platforms.find( @@ -240,7 +238,7 @@ onMounted(async () => { ) { romsStore.setCurrentPlatform(platform); resetGallery(); - fetchRoms(); + await fetchRoms(); setFilters(); } @@ -274,7 +272,7 @@ onBeforeRouteUpdate(async (to, from) => { watch( () => allPlatforms.value, - (platforms) => { + async (platforms) => { if (platforms.length > 0) { const platform = platforms.find( (platform) => platform.id === routePlatformId, @@ -287,7 +285,7 @@ onBeforeRouteUpdate(async (to, from) => { platform ) { romsStore.setCurrentPlatform(platform); - fetchRoms(); + await fetchRoms(); setFilters(); } else { noPlatformError.value = true; From 68af8b0df1340e71df01f1f5c1dc1f5d0e0a7db7 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Thu, 16 Jan 2025 17:18:01 -0500 Subject: [PATCH 14/44] Remove labels from issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 1 - .github/ISSUE_TEMPLATE/custom.md | 1 - .github/ISSUE_TEMPLATE/feature_request.md | 1 - 3 files changed, 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 210c696ca..96f1eefc8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,6 @@ name: Bug report about: Report a bug, issue or problem title: "[Bug] Bug title" -labels: bug assignees: "" --- diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md index 425fe62cf..c994e3653 100644 --- a/.github/ISSUE_TEMPLATE/custom.md +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -2,6 +2,5 @@ name: Custom issue template about: Describe this issue template's purpose here. title: "[Other] Custom issue title" -labels: other assignees: "" --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 0db4178cd..f74cf4009 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,6 @@ name: Feature request about: Suggest an idea for this project title: "[Feature] Feature title" -labels: feature assignees: "" --- From fb622ff990df1ed616d6d157f87f9b7644f2ceff Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sat, 18 Jan 2025 19:50:35 -0300 Subject: [PATCH 15/44] fix: Improve support for older PostgreSQL versions Functions like `JSON_ARRAY` and `JSON_OBJECT` have been introduced in PostgreSQL 16 [1]. Older PostgreSQL versions were failing to run the migrations due to the usage of these functions. This change uses `jsonb_build_array` and `jsonb_build_object` functions in PostgreSQL, which have been available since PostgreSQL 9.5 [2]. [1]: https://www.postgresql.org/docs/release/16.0/ [2]: https://www.postgresql.org/docs/release/9.5.0/#AEN132010 --- .../alembic/versions/0009_models_refactor.py | 18 +++++++++++++----- .../versions/0012_add_regions_languages.py | 13 ++++++++----- backend/alembic/versions/0014_asset_files.py | 5 ++++- .../alembic/versions/0015_mobygames_data.py | 12 +++++++----- backend/alembic/versions/1.8.1_.py | 14 +++++++++----- 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/backend/alembic/versions/0009_models_refactor.py b/backend/alembic/versions/0009_models_refactor.py index 189462beb..9a48ea38e 100644 --- a/backend/alembic/versions/0009_models_refactor.py +++ b/backend/alembic/versions/0009_models_refactor.py @@ -9,7 +9,7 @@ Create Date: 2023-09-12 18:18:27.158732 import sqlalchemy as sa from alembic import op from sqlalchemy.exc import OperationalError -from utils.database import CustomJSON +from utils.database import CustomJSON, is_postgresql # revision identifiers, used by Alembic. revision = "0009_models_refactor" @@ -21,6 +21,10 @@ depends_on = None def upgrade() -> None: connection = op.get_bind() + json_array_build_func = ( + "jsonb_build_array()" if is_postgresql(connection) else "JSON_ARRAY()" + ) + try: with op.batch_alter_table("platforms", schema=None) as batch_op: batch_op.alter_column( @@ -87,13 +91,13 @@ def upgrade() -> None: "url_screenshots", existing_type=CustomJSON(), nullable=True, - existing_server_default=sa.text("(JSON_ARRAY())"), + existing_server_default=sa.text(f"({json_array_build_func})"), ) batch_op.alter_column( "path_screenshots", existing_type=CustomJSON(), nullable=True, - existing_server_default=sa.text("(JSON_ARRAY())"), + existing_server_default=sa.text(f"({json_array_build_func})"), ) try: @@ -108,6 +112,10 @@ def upgrade() -> None: def downgrade() -> None: connection = op.get_bind() + json_array_build_func = ( + "jsonb_build_array()" if is_postgresql(connection) else "JSON_ARRAY()" + ) + with op.batch_alter_table("roms", schema=None) as batch_op: batch_op.alter_column( "igdb_id", @@ -136,13 +144,13 @@ def downgrade() -> None: "path_screenshots", existing_type=CustomJSON(), nullable=False, - existing_server_default=sa.text("(JSON_ARRAY())"), + existing_server_default=sa.text(f"({json_array_build_func})"), ) batch_op.alter_column( "url_screenshots", existing_type=CustomJSON(), nullable=False, - existing_server_default=sa.text("(JSON_ARRAY())"), + existing_server_default=sa.text(f"({json_array_build_func})"), ) batch_op.alter_column( "file_size_units", existing_type=sa.VARCHAR(length=10), nullable=True diff --git a/backend/alembic/versions/0012_add_regions_languages.py b/backend/alembic/versions/0012_add_regions_languages.py index 3cb2029da..b60010abe 100644 --- a/backend/alembic/versions/0012_add_regions_languages.py +++ b/backend/alembic/versions/0012_add_regions_languages.py @@ -18,19 +18,22 @@ depends_on = None def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### + connection = op.get_bind() + with op.batch_alter_table("roms", schema=None) as batch_op: batch_op.add_column(sa.Column("regions", CustomJSON(), nullable=True)) batch_op.add_column(sa.Column("languages", CustomJSON(), nullable=True)) with op.batch_alter_table("roms", schema=None) as batch_op: # Set default values for languages and regions - batch_op.execute("UPDATE roms SET languages = JSON_ARRAY()") - batch_op.execute("UPDATE roms SET regions = JSON_ARRAY(region)") + if is_postgresql(connection): + batch_op.execute("UPDATE roms SET languages = jsonb_build_array()") + batch_op.execute("UPDATE roms SET regions = jsonb_build_array(region)") + else: + batch_op.execute("UPDATE roms SET languages = JSON_ARRAY()") + batch_op.execute("UPDATE roms SET regions = JSON_ARRAY(region)") batch_op.drop_column("region") - # ### end Alembic commands ### - def downgrade() -> None: connection = op.get_bind() diff --git a/backend/alembic/versions/0014_asset_files.py b/backend/alembic/versions/0014_asset_files.py index f1c994301..cc433c261 100644 --- a/backend/alembic/versions/0014_asset_files.py +++ b/backend/alembic/versions/0014_asset_files.py @@ -191,7 +191,10 @@ def upgrade() -> None: # Move data around with op.batch_alter_table("roms", schema=None) as batch_op: - batch_op.execute("update roms set igdb_metadata = JSON_OBJECT()") + if is_postgresql(connection): + batch_op.execute("update roms set igdb_metadata = jsonb_build_object()") + else: + batch_op.execute("update roms set igdb_metadata = JSON_OBJECT()") batch_op.execute( "update roms set path_cover_s = '', path_cover_l = '', url_cover = '' where url_cover = 'https://images.igdb.com/igdb/image/upload/t_cover_big/nocover.png'" ) diff --git a/backend/alembic/versions/0015_mobygames_data.py b/backend/alembic/versions/0015_mobygames_data.py index f8ca0e1e2..b402b7340 100644 --- a/backend/alembic/versions/0015_mobygames_data.py +++ b/backend/alembic/versions/0015_mobygames_data.py @@ -8,7 +8,7 @@ Create Date: 2024-02-13 17:57:25.936825 import sqlalchemy as sa from alembic import op -from utils.database import CustomJSON +from utils.database import CustomJSON, is_postgresql # revision identifiers, used by Alembic. revision = "0015_mobygames_data" @@ -18,7 +18,8 @@ depends_on = None def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### + connection = op.get_bind() + with op.batch_alter_table("platforms", schema=None) as batch_op: batch_op.add_column(sa.Column("moby_id", sa.Integer(), nullable=True)) @@ -27,9 +28,10 @@ def upgrade() -> None: batch_op.add_column(sa.Column("moby_metadata", CustomJSON(), nullable=True)) with op.batch_alter_table("roms", schema=None) as batch_op: - batch_op.execute("update roms set moby_metadata = JSON_OBJECT()") - - # ### end Alembic commands ### + if is_postgresql(connection): + batch_op.execute("update roms set moby_metadata = jsonb_build_object()") + else: + batch_op.execute("update roms set moby_metadata = JSON_OBJECT()") def downgrade() -> None: diff --git a/backend/alembic/versions/1.8.1_.py b/backend/alembic/versions/1.8.1_.py index 1ea4b759b..258405f71 100644 --- a/backend/alembic/versions/1.8.1_.py +++ b/backend/alembic/versions/1.8.1_.py @@ -8,7 +8,7 @@ Create Date: 2023-04-17 12:03:19.163501 import sqlalchemy as sa from alembic import op -from utils.database import CustomJSON +from utils.database import CustomJSON, is_postgresql # revision identifiers, used by Alembic. revision = "1.8.1" @@ -18,14 +18,19 @@ depends_on = None def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### + connection = op.get_bind() + + json_array_build_func = ( + "jsonb_build_array()" if is_postgresql(connection) else "JSON_ARRAY()" + ) + with op.batch_alter_table("roms") as batch_op: batch_op.add_column( sa.Column( "url_screenshots", CustomJSON(), nullable=False, - server_default=sa.text("(JSON_ARRAY())"), + server_default=sa.text(f"({json_array_build_func})"), ) ) batch_op.add_column( @@ -33,10 +38,9 @@ def upgrade() -> None: "path_screenshots", CustomJSON(), nullable=False, - server_default=sa.text("(JSON_ARRAY())"), + server_default=sa.text(f"({json_array_build_func})"), ) ) - # ### end Alembic commands ### def downgrade() -> None: From 987aa2318462d44eb22748b663059bbcbd2eb3a5 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sun, 19 Jan 2025 21:28:32 -0300 Subject: [PATCH 16/44] fix: English translation value for "collections" Fixes #1508. --- frontend/src/locales/en_GB/rom.json | 2 +- frontend/src/locales/en_US/rom.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/locales/en_GB/rom.json b/frontend/src/locales/en_GB/rom.json index 66068c4f1..e65c8b297 100644 --- a/frontend/src/locales/en_GB/rom.json +++ b/frontend/src/locales/en_GB/rom.json @@ -37,7 +37,7 @@ "tags": "Tags", "genres": "Genres", "franchises": "Franchises", - "collections": "Collecciones", + "collections": "Collections", "companies": "Companies", "age-rating": "Age rating", "no-saves-found": "No saves found", diff --git a/frontend/src/locales/en_US/rom.json b/frontend/src/locales/en_US/rom.json index 66068c4f1..e65c8b297 100644 --- a/frontend/src/locales/en_US/rom.json +++ b/frontend/src/locales/en_US/rom.json @@ -37,7 +37,7 @@ "tags": "Tags", "genres": "Genres", "franchises": "Franchises", - "collections": "Collecciones", + "collections": "Collections", "companies": "Companies", "age-rating": "Age rating", "no-saves-found": "No saves found", From c018c75c355ba7be35455086e131f8bcd957fce2 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Mon, 20 Jan 2025 00:22:52 -0300 Subject: [PATCH 17/44] misc: Add useful profiling tools to dev dependencies `pyinstrument` for code profiling. `memray` for memory profiling. --- poetry.lock | 310 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 8 +- 2 files changed, 315 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 243caf80e..2cd48e121 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1016,6 +1016,25 @@ docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alab qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] +[[package]] +name = "jinja2" +version = "3.1.5" +description = "A very fast and expressive template engine." +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] name = "joserfc" version = "0.9.0" @@ -1080,6 +1099,28 @@ traitlets = ">=5.3" docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] +[[package]] +name = "linkify-it-py" +version = "2.0.3" +description = "Links recognition library with FULL unicode support." +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, + {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, +] + +[package.dependencies] +uc-micro-py = "*" + +[package.extras] +benchmark = ["pytest", "pytest-benchmark"] +dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"] +doc = ["myst-parser", "sphinx", "sphinx-book-theme"] +test = ["coverage", "pytest", "pytest-cov"] + [[package]] name = "mako" version = "1.3.8" @@ -1124,6 +1165,34 @@ files = [ [package.dependencies] packaging = "*" +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +linkify-it-py = {version = ">=1,<3", optional = true, markers = "extra == \"linkify\""} +mdit-py-plugins = {version = "*", optional = true, markers = "extra == \"plugins\""} +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "3.0.2" @@ -1211,6 +1280,106 @@ files = [ [package.dependencies] traitlets = "*" +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +description = "Collection of plugins for markdown-it-py" +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, + {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, +] + +[package.dependencies] +markdown-it-py = ">=1.0.0,<4.0.0" + +[package.extras] +code-style = ["pre-commit"] +rtd = ["myst-parser", "sphinx-book-theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "memray" +version = "1.15.0" +description = "A memory profiler for Python applications" +optional = true +python-versions = ">=3.7.0" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "memray-1.15.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:9b623c0c651d611dd068236566a8a202250e3d59307c3a3f241acc47835e73eb"}, + {file = "memray-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74765f92887b7eed152e3b9f14c147c43bf0247417b18c7ea0dec173cd01633c"}, + {file = "memray-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a5c6be5f9c2280b5ba077cbfec4706f209f9c0c2cd3a53d949ab9f4ee1f6a255"}, + {file = "memray-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:68bdad519b644539440914e1f6a04995631d0e31311ebe0977d949f2125bb579"}, + {file = "memray-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4964c6bd555a0f1755dfdb97a8d9864e646054594449c66757441f7d7682405"}, + {file = "memray-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92212b85c7d843126e4d343c8ca024f4a57537017b9ac7611864963b322aafae"}, + {file = "memray-1.15.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:cb8997e113378b9ac8bbd9b17f4f867fc5c1eea1980d873be3ebe4c2f1176784"}, + {file = "memray-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ee45d919d81bfeb33677357dd5d248f3cad1d56be2ebd1853d4615a9f965b11"}, + {file = "memray-1.15.0-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a6b740aad69e7e5f82ffff53a8edef1313ff0b5e9b7253912da16e905dcb1dcb"}, + {file = "memray-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0045611f2da496e35d37a5ddfa2b6a74bbc82e47087924c07b3f520448297b26"}, + {file = "memray-1.15.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca5688e33a833de604d0e2de01b5bf11a4ac1d768998f8831a375a343dc7acaf"}, + {file = "memray-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bbad938c3fdcebe0cf3c568fb8f8633ab37ab08ad4db167e0991e214d6f595b"}, + {file = "memray-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4eb50295bd87a091a85ec71f0ee612c5d709df490fea8a3adc4410f5da4f695"}, + {file = "memray-1.15.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:d13554a25129593872b5fbcd55ac34453239e51d9b6ace258329596ccce22bb3"}, + {file = "memray-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8cfe15962a9002ede8b1f8b4f045d95855100a8a60a9bf0d9f2b92950f914189"}, + {file = "memray-1.15.0-cp312-cp312-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e84b39adca05e720bdbf950cc92ef4bafefa2d6160111e5fc427cf59c6c16d1a"}, + {file = "memray-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7745d2c58dfc33ef77f8827053cb957131420051b67e2d5642b605d0e65a586"}, + {file = "memray-1.15.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:412225d85db0ec22142a82646d85ecc1e8680d33adbfd15789c7eaa356ad4107"}, + {file = "memray-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25ab7a7e32fedab46219121dfb6ec3e42c66984b217572fdd4cddc37359c521"}, + {file = "memray-1.15.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fb885f92833279d34addc607831352e91267b8e547ea861ad561a3dba64f6757"}, + {file = "memray-1.15.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:c1308e6a5fc5bc4e183bc0fdf5e241ddd9fb374338f32d77a4d5e74ccf611ef1"}, + {file = "memray-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0794227dfa4b86a56137211fd5b8ec131e0bc4a5dc41c2f5a318ca56a22c9331"}, + {file = "memray-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f184e82debd4f0c8ecf8e6034efddccdd9fac22909553a7f094eabf0902cd53f"}, + {file = "memray-1.15.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3493c5ac1ae1353fd0d24481bc9f30da8960ef703bf4af966cefff9dd1234d38"}, + {file = "memray-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:145a3062d8bf631aa8dc4b0928585e201282634afc369799dae1a0b9ece59fd4"}, + {file = "memray-1.15.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:59a4ade09cfe46e85cdb3a1976e9768e4674a6e448533c415dbe84e5a834f7c3"}, + {file = "memray-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bb9870f41fe0c4cd4612fded51174f5b837f3bc6364c57b4a60e65016ccc1f7a"}, + {file = "memray-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:26cb3cac3810bbe9e701d40463c185cf9e7faac3965a0e2b309df1a9fc18cd9a"}, + {file = "memray-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:317287025cabd541f9fdec615b3c7ff394798113feea0edb92d31bc9f06eafd0"}, + {file = "memray-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:850eba1e3063d97172b0990a14f61782682baeb48f6ae039c0bb86b2f4d19b75"}, + {file = "memray-1.15.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:aa5150e3b58ba6184fac2a97426ee66f996dffe0571bbf09bffe23836318772e"}, + {file = "memray-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:753632eed43161131bb632799dc53b7ccb7e6341b8ca8ef4ad68ff8da81e766a"}, + {file = "memray-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:727190a81516e1955932c307ac6a55a3aedb5799bc2edf6a8fbf49852e851f0c"}, + {file = "memray-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:413b145445110900a99fb78b1fb6932c2e3ffadd35df5b258f8ac0a25e0aaf90"}, + {file = "memray-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2518a298ffa6c5a2ddfa6a36d196aa4aef5bb33c5d95a26565aac6a7f5fcb0c0"}, + {file = "memray-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae46cb726c4c06121614995b877365680f196fa4549698aa5026c494a40e1a24"}, + {file = "memray-1.15.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:ce28c6a4d89349c43d76ad35ff1c21057230086cfcf18c6f4c2305df108bf0cd"}, + {file = "memray-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:671c2fd8c835caad80c2023baf6cdc4326c0f6dd4ae8bf1d7dbf6ad700c13625"}, + {file = "memray-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8404f3969e071e35364fd99d238da8ef245cf7ee2c790f3d46cd5b41cbac0541"}, + {file = "memray-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a3e4c940deae29ea64d8dd4ffaee804f541a413c3c3c061a469837ed35d486b7"}, + {file = "memray-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36720d9ee97dee6cd51b230cbd2556cc3e0215c5a569b97c1faebc927ac3c505"}, + {file = "memray-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cba7727bfdee596f71323195af0262508ed0aec7ebbf67d98de0b959d9b8cf02"}, + {file = "memray-1.15.0.tar.gz", hash = "sha256:1beffa2bcba3dbe0f095d547927286eca46e272798b83026dd1b5db58e16ed56"}, +] + +[package.dependencies] +jinja2 = ">=2.9" +rich = ">=11.2.0" +textual = ">=0.41.0" + +[package.extras] +benchmark = ["asv"] +dev = ["Cython", "IPython", "asv", "black", "bump2version", "check-manifest", "flake8", "furo", "greenlet", "ipython", "isort", "mypy", "packaging", "pytest", "pytest-cov", "pytest-textual-snapshot", "setuptools", "sphinx", "sphinx-argparse", "textual (>=0.43,!=0.65.2,!=0.66)", "towncrier"] +docs = ["IPython", "bump2version", "furo", "sphinx", "sphinx-argparse", "towncrier"] +lint = ["black", "check-manifest", "flake8", "isort", "mypy"] +test = ["Cython", "greenlet", "ipython", "packaging", "pytest", "pytest-cov", "pytest-textual-snapshot", "setuptools", "textual (>=0.43,!=0.65.2,!=0.66)"] + [[package]] name = "multidict" version = "6.1.0" @@ -2166,6 +2335,85 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyinstrument" +version = "5.0.0" +description = "Call stack profiler for Python. Shows you why your code is slow!" +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "pyinstrument-5.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6a83cf18f5594e1b1899b12b46df7aabca556eef895846ccdaaa3a46a37d1274"}, + {file = "pyinstrument-5.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1cc236313272d0222261be8e2b2a08e42d7ccbe54db9059babf4d77040da1880"}, + {file = "pyinstrument-5.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd685d68a31f3715ca61f82c37c1c2f8b75f45646bd9840e04681d91862bd85"}, + {file = "pyinstrument-5.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cecd0f6558f13fba74a9f036b2b168956206e9525dcb84c6add2d73ab61dc22"}, + {file = "pyinstrument-5.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40a8485c2e41082a20822001a6651667bb5327f6f5f6759987198593e45bb376"}, + {file = "pyinstrument-5.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a6294b7111348765ba4c311fc91821ed8b59c6690c4dab23aa7165a67da9e972"}, + {file = "pyinstrument-5.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a164f3dae5c7db2faa501639659d64034cde8db62a4d6744712593a369bc8629"}, + {file = "pyinstrument-5.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f6bac8a434407de6f2ebddbcdecdb19b324c9315cbb8b8c2352714f7ced8181"}, + {file = "pyinstrument-5.0.0-cp310-cp310-win32.whl", hash = "sha256:7e8dc887e535f5c5e5a2a64a0729496f11ddcef0c23b0a555d5ab6fa19759445"}, + {file = "pyinstrument-5.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c337190a1818841732643ba93065411591df526bc9de44b97ba8f56b581d2ef"}, + {file = "pyinstrument-5.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c9052f548ec5ccecc50676fbf1a1d0b60bdbd3cd67630c5253099af049d1f0ad"}, + {file = "pyinstrument-5.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:197d25487f52da3f8ec26d46db7202bc5d703cc73c1503371166417eb7cea14e"}, + {file = "pyinstrument-5.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a072d928dc16a32e0f3d1e51726f4472a69d66d838ee1d1bf248737fd70b9415"}, + {file = "pyinstrument-5.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2c7ae2c984879a645fce583bf3053b7e57495f60c1e158bb71ad7dfced1fbf1"}, + {file = "pyinstrument-5.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8284bf8847629c9a5054702b9306eab3ab14c2474959e01e606369ffbcf938bc"}, + {file = "pyinstrument-5.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fd94cc725efb1dd41ae8e20a5f06a6a5363dec959e8a9dacbac3f4d12d28f03"}, + {file = "pyinstrument-5.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e0fdb9fe6f9c694940410dcc82e23a3fe2928114328efd35047fc0bb8a6c959f"}, + {file = "pyinstrument-5.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ffe938e63173ceb8ce7b6b309ce26c9d44d16f53c0162d89d6e706eb9e69802"}, + {file = "pyinstrument-5.0.0-cp311-cp311-win32.whl", hash = "sha256:80d2a248516f372a89e0fe9ddf4a9d6388a4c6481b6ebd3dfe01b3cd028c0275"}, + {file = "pyinstrument-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:7ccf4267aff62de0e1d976e8f5da25dcb69737ae86e38d3cfffa24877837e7d1"}, + {file = "pyinstrument-5.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:dec3529a5351ea160baeef1ef2a6e28b1a7a7b3fb5e9863fae8de6da73d0f69a"}, + {file = "pyinstrument-5.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a39e3ef84c56183f8274dfd584b8c2fae4783c6204f880513e70ab2440b9137"}, + {file = "pyinstrument-5.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3938f063ee065e05826628dadf1fb32c7d26b22df4a945c22f7fe25ea1ba6a2"}, + {file = "pyinstrument-5.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f18990cc16b2e23b54738aa2f222863e1d36daaaec8f67b1613ddfa41f5b24db"}, + {file = "pyinstrument-5.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3731412b5bfdcef8014518f145140c69384793e218863a33a39ccfe5fb42045"}, + {file = "pyinstrument-5.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02b2eaf38460b14eea646d6bb7f373eb5bb5691d13f788e80bdcb3a4eaa2519e"}, + {file = "pyinstrument-5.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e57db06590f13657b2bce8c4d9cf8e9e2bd90bb729bcbbe421c531ba67ad7add"}, + {file = "pyinstrument-5.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ddaa3001c1b798ec9bf1266ef476bbc0834b74d547d531f5ed99e7d05ac5d81b"}, + {file = "pyinstrument-5.0.0-cp312-cp312-win32.whl", hash = "sha256:b69ff982acf5ef2f4e0f32ce9b4b598f256faf88438f233ea3a72f1042707e5b"}, + {file = "pyinstrument-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bf4ef061d60befe72366ce0ed4c75dee5be089644de38f9936d2df0bcf44af0"}, + {file = "pyinstrument-5.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79a54def2d4aa83a4ed37c6cffc5494ae5de140f0453169eb4f7c744cc249d3a"}, + {file = "pyinstrument-5.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9538f746f166a40c8802ebe5c3e905d50f3faa189869cd71c083b8a639e574bb"}, + {file = "pyinstrument-5.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bbab65cae1483ad8a18429511d1eac9e3efec9f7961f2fd1bf90e1e2d69ef15"}, + {file = "pyinstrument-5.0.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4351ad041d208c597e296a0e9c2e6e21cc96804608bcafa40cfa168f3c2b8f79"}, + {file = "pyinstrument-5.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceee5252f4580abec29bcc5c965453c217b0d387c412a5ffb8afdcda4e648feb"}, + {file = "pyinstrument-5.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b3050a4e7033103a13cfff9802680e2070a9173e1a258fa3f15a80b4eb9ee278"}, + {file = "pyinstrument-5.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3b1f44a34da7810938df615fb7cbc43cd879b42ca6b5cd72e655aee92149d012"}, + {file = "pyinstrument-5.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fde075196c8a3b2be191b8da05b92ff909c78d308f82df56d01a8cfdd6da07b9"}, + {file = "pyinstrument-5.0.0-cp313-cp313-win32.whl", hash = "sha256:1a9b62a8b54e05e7723eb8b9595fadc43559b73290c87b3b1cb2dc5944559790"}, + {file = "pyinstrument-5.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2478d2c55f77ad8e281e67b0dfe7c2176304bb824c307e86e11890f5e68d7feb"}, + {file = "pyinstrument-5.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c2e3b4283f85232fd5818e2153e6798bceb39a8c3ccfaa22fae08faf554740b7"}, + {file = "pyinstrument-5.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fb1139d2822abff1cbf1c81c018341f573b7afa23a94ce74888a0f6f47828cbc"}, + {file = "pyinstrument-5.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c971566d86ba46a7233d3f5b0d85d7ee4c9863f541f5d8f796c3947ebe17f68"}, + {file = "pyinstrument-5.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:429376235960179d6ab9b97e7871090059d39de160b4e3b2723672f30e8eea8e"}, + {file = "pyinstrument-5.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8599b4b0630c776b30fc3c4f7476d5e3814ee7fe42d99131644fe3c00b40fdf1"}, + {file = "pyinstrument-5.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a8bc688afa2a5368042a7cb56866d5a28fdff8f37a282f7be79b17cae042841b"}, + {file = "pyinstrument-5.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5d34c06e2276d1f549a540bccb063688ea3d876e6df7c391205f1c8b4b96d5c8"}, + {file = "pyinstrument-5.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d3b2ec6e028731dbb2ba8cf06f19030162789e6696bca990a09519881ad42fb"}, + {file = "pyinstrument-5.0.0-cp38-cp38-win32.whl", hash = "sha256:5ed6f5873a7526ec5915e45d956d044334ef302653cf63649e48c41561aaa285"}, + {file = "pyinstrument-5.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:9e87d65bae7d0f5ef50908e35d67d43b7cc566909995cc99e91721bb49b4ea06"}, + {file = "pyinstrument-5.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bd953163616bc29c2ccb1e4c0e48ccdd11e0a97fc849da26bc362bba372019ba"}, + {file = "pyinstrument-5.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d2a7279ed9b6d7cdae247bc2e57095a32f35dfe32182c334ab0ac3eb02e0eac"}, + {file = "pyinstrument-5.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68001dfcb8a37b624a1c3de5d2ee7d634f63eac7a6dd1357b7370a5cdbdcf567"}, + {file = "pyinstrument-5.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5c4c3cc6410ad5afe0e352a7fb09fb1ab85eb5676ec5ec8522123759d9cc68f"}, + {file = "pyinstrument-5.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d87ddab66b1b3525ad3abc49a88aaa51efcaf83578e9d2a702c03a1cea39f28"}, + {file = "pyinstrument-5.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03182ffaa9c91687cbaba80dc0c5a47015c5ea170fe642f632d88e885cf07356"}, + {file = "pyinstrument-5.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:39b60417c9c12eed04e1886644e92aa0b281d72e5d0b097b16253cade43110f7"}, + {file = "pyinstrument-5.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7bb389b6d1573361bd1367b296133c5c69184e35fc18db22e29e8cdf56f158f9"}, + {file = "pyinstrument-5.0.0-cp39-cp39-win32.whl", hash = "sha256:ae69478815edb3c63e7ebf82e1e13e38c3fb2bab833b1c013643c3475b1b8cf5"}, + {file = "pyinstrument-5.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:83caeb4150c0334e9e290c0f9bb164ff6bdc199065ecb62016268e8a88589a51"}, + {file = "pyinstrument-5.0.0.tar.gz", hash = "sha256:144f98eb3086667ece461f66324bf1cc1ee0475b399ab3f9ded8449cc76b7c90"}, +] + +[package.extras] +bin = ["click", "nox"] +docs = ["furo (==2024.7.18)", "myst-parser (==3.0.1)", "sphinx (==7.4.7)", "sphinx-autobuild (==2024.4.16)", "sphinxcontrib-programoutput (==0.17)"] +examples = ["django", "litestar", "numpy"] +test = ["cffi (>=1.17.0)", "flaky", "greenlet (>=3)", "ipython", "pytest", "pytest-asyncio (==0.23.8)", "trio"] +types = ["typing-extensions"] + [[package]] name = "pyppmd" version = "1.1.0" @@ -2771,6 +3019,26 @@ files = [ hiredis = ["hiredis (>=3.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"] +[[package]] +name = "rich" +version = "13.9.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = true +python-versions = ">=3.8.0" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "rq" version = "1.16.2" @@ -3141,6 +3409,28 @@ files = [ {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, ] +[[package]] +name = "textual" +version = "1.0.0" +description = "Modern Text User Interface framework" +optional = true +python-versions = "<4.0.0,>=3.8.1" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "textual-1.0.0-py3-none-any.whl", hash = "sha256:2d4a701781c05104925e463ae370c630567c70c2880e92ab838052e3e23c986f"}, + {file = "textual-1.0.0.tar.gz", hash = "sha256:bec9fe63547c1c552569d1b75d309038b7d456c03f86dfa3706ddb099b151399"}, +] + +[package.dependencies] +markdown-it-py = {version = ">=2.1.0", extras = ["linkify", "plugins"]} +platformdirs = ">=3.6.0,<5" +rich = ">=13.3.3" +typing-extensions = ">=4.4.0,<5.0.0" + +[package.extras] +syntax = ["tree-sitter (>=0.23.0)", "tree-sitter-bash (>=0.23.0)", "tree-sitter-css (>=0.23.0)", "tree-sitter-go (>=0.23.0)", "tree-sitter-html (>=0.23.0)", "tree-sitter-java (>=0.23.0)", "tree-sitter-javascript (>=0.23.0)", "tree-sitter-json (>=0.24.0)", "tree-sitter-markdown (>=0.3.0)", "tree-sitter-python (>=0.23.0)", "tree-sitter-regex (>=0.24.0)", "tree-sitter-rust (>=0.23.0)", "tree-sitter-sql (>=0.3.0)", "tree-sitter-toml (>=0.6.0)", "tree-sitter-xml (>=0.7.0)", "tree-sitter-yaml (>=0.6.0)"] + [[package]] name = "tornado" version = "6.4.2" @@ -3300,6 +3590,22 @@ files = [ {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] +[[package]] +name = "uc-micro-py" +version = "1.0.3" +description = "Micro subset of unicode data files for linkify-it-py projects." +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"dev\"" +files = [ + {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, + {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, +] + +[package.extras] +test = ["coverage", "pytest", "pytest-cov"] + [[package]] name = "unidecode" version = "1.3.8" @@ -3747,10 +4053,10 @@ files = [ ] [extras] -dev = ["ipdb", "ipykernel", "mypy"] +dev = ["ipdb", "ipykernel", "memray", "mypy", "pyinstrument"] test = ["fakeredis", "pytest", "pytest-asyncio", "pytest-env", "pytest-mock", "pytest-recording"] [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "3c92517a9a6abe41cc7d465f843f4ba4e8043eb2fd9dd352dafcb8bae0a7dff9" +content-hash = "00da00f53b045762b766cd778f2e71d971742dc85521e10f033fc228e4d07274" diff --git a/pyproject.toml b/pyproject.toml index c22fb39c2..8a3e5b47f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,13 @@ dependencies = [ ] [project.optional-dependencies] -dev = ["ipdb ~= 0.13", "ipykernel ~= 6.29", "mypy ~= 1.13"] +dev = [ + "ipdb ~= 0.13", + "ipykernel ~= 6.29", + "memray ~= 1.15", + "mypy ~= 1.13", + "pyinstrument ~= 5.0", +] test = [ "fakeredis ~= 2.21", "pytest ~= 8.3", From 6f5e0a0ed90e2740cb279331017fffe0bc58b235 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Mon, 20 Jan 2025 00:28:43 -0300 Subject: [PATCH 18/44] misc: Do not fail if LOGLEVEL is not uppercase The `logging.setLevel` method expects the log level to be in uppercase. This change ensures that the `LOGLEVEL` config value is always in uppercase. --- backend/config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/config/__init__.py b/backend/config/__init__.py index 238ab0258..e4ba1e0cd 100644 --- a/backend/config/__init__.py +++ b/backend/config/__init__.py @@ -117,7 +117,7 @@ DISABLE_RUFFLE_RS = str_to_bool(os.environ.get("DISABLE_RUFFLE_RS", "false")) UPLOAD_TIMEOUT = int(os.environ.get("UPLOAD_TIMEOUT", 600)) # LOGGING -LOGLEVEL: Final = os.environ.get("LOGLEVEL", "INFO") +LOGLEVEL: Final = os.environ.get("LOGLEVEL", "INFO").upper() FORCE_COLOR: Final = str_to_bool(os.environ.get("FORCE_COLOR", "false")) NO_COLOR: Final = str_to_bool(os.environ.get("NO_COLOR", "false")) From 1cc22ecd250dc95824b5b2e8a534f415d742f1c0 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Mon, 20 Jan 2025 17:48:03 -0500 Subject: [PATCH 19/44] [ROMM-1503] Remember search query when going back to page --- .../Gallery/AppBar/Search/SearchTextField.vue | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Gallery/AppBar/Search/SearchTextField.vue b/frontend/src/components/Gallery/AppBar/Search/SearchTextField.vue index 18648bcb5..d6603fee0 100644 --- a/frontend/src/components/Gallery/AppBar/Search/SearchTextField.vue +++ b/frontend/src/components/Gallery/AppBar/Search/SearchTextField.vue @@ -5,15 +5,17 @@ import storeGalleryFilter from "@/stores/galleryFilter"; import storePlatforms from "@/stores/platforms"; import type { Events } from "@/types/emitter"; import type { Emitter } from "mitt"; -import { inject } from "vue"; +import { inject, onMounted, watch } from "vue"; import { storeToRefs } from "pinia"; import { useDisplay } from "vuetify"; +import { useRouter } from "vue-router"; import type { Platform } from "@/stores/platforms"; import { useI18n } from "vue-i18n"; // Props const { xs } = useDisplay(); const { t } = useI18n(); +const router = useRouter(); const romsStore = storeRoms(); const { gettingRoms } = storeToRefs(romsStore); const emitter = inject>("emitter"); @@ -67,6 +69,8 @@ async function fetchRoms() { const inputElement = document.getElementById("search-text-field"); inputElement?.blur(); gettingRoms.value = true; + // Update URL with search term + router.replace({ query: { search: searchText.value } }); try { const { data } = await romApi.getRoms({ searchTerm: searchText.value }); @@ -101,6 +105,25 @@ async function fetchRoms() { } } } + +onMounted(() => { + const { search: searchTerm } = router.currentRoute.value.query; + if (searchTerm && searchTerm !== searchText.value) { + searchText.value = searchTerm as string; + fetchRoms(); + } +}); + +watch( + router.currentRoute.value.query, + (query) => { + if (query.search && query.search !== searchText.value) { + searchText.value = query.search as string; + fetchRoms(); + } + }, + { deep: true }, +);