Run library, checks and downloads in background threads with parallel fetching
This commit is contained in:
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.2.0] — 2026-06-06
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Background threading — library refresh, update checks and downloads now run off the GUI thread, so the app no longer freezes during network or disk I/O
|
||||||
|
- Parallel product-info fetching — up to 16 concurrent requests dramatically speed up library refresh and update checks (previously one request per game, sequentially)
|
||||||
|
- Download progress — per-file byte progress bar with live transfer speed, plus a Cancel button to abort an in-progress download
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- "Scan Existing Installers" now matches sanitized folder names and marks detected games as managed, so they appear checked in the Library tab
|
||||||
|
- In-session caching of product info and owned game IDs in the GOG API client, cleared at the start of each manual refresh
|
||||||
|
- Atomic writes for `config.json`, metadata and auth tokens — a crash mid-write can no longer corrupt them
|
||||||
|
- `User-Agent` header now reports the real application version
|
||||||
|
- File logging to `~/.config/gogupdater/gogupdater.log` (2 MB rotation) plus verbose diagnostics and logging of uncaught exceptions raised inside Qt slots
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Background workers were garbage-collected before running, so "Refresh Library" and "Check for Updates" did nothing — workers are now kept alive until their thread finishes
|
||||||
|
- Download resume no longer corrupts files when the server ignores the HTTP `Range` request (falls back to a full re-download)
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- Added unit tests for folder-name sanitization, version comparison, prune strategies, atomic writes and the threading helpers; rewrote the stale constants test to match the current module
|
||||||
|
|
||||||
## [1.1.0] — 2026-05-13
|
## [1.1.0] — 2026-05-13
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -12,10 +12,33 @@ from src.constants import APP_TITLE
|
|||||||
from src.ui.main_window import MainWindow
|
from src.ui.main_window import MainWindow
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_logging(config_dir) -> None:
|
||||||
|
"""Log DEBUG to stderr and to a rotating file for diagnostics."""
|
||||||
|
log_file = config_dir / "gogupdater.log"
|
||||||
|
logger.add(
|
||||||
|
log_file,
|
||||||
|
level="DEBUG",
|
||||||
|
rotation="2 MB",
|
||||||
|
retention=3,
|
||||||
|
backtrace=True,
|
||||||
|
diagnose=True,
|
||||||
|
enqueue=True, # thread-safe logging from worker threads
|
||||||
|
)
|
||||||
|
logger.info(f"Logging to {log_file}")
|
||||||
|
|
||||||
|
# Make sure exceptions raised inside Qt slots are logged instead of swallowed.
|
||||||
|
def _excepthook(exc_type, exc_value, exc_tb):
|
||||||
|
logger.opt(exception=(exc_type, exc_value, exc_tb)).error("Uncaught exception")
|
||||||
|
sys.__excepthook__(exc_type, exc_value, exc_tb)
|
||||||
|
|
||||||
|
sys.excepthook = _excepthook
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
config_dir = DEFAULT_CONFIG_DIR
|
config_dir = DEFAULT_CONFIG_DIR
|
||||||
config_dir.mkdir(parents=True, exist_ok=True)
|
config_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
_setup_logging(config_dir)
|
||||||
logger.info(f"Starting {APP_TITLE}")
|
logger.info(f"Starting {APP_TITLE}")
|
||||||
|
|
||||||
auth = AuthManager(config_dir)
|
auth = AuthManager(config_dir)
|
||||||
|
|||||||
Generated
+351
-346
@@ -1,154 +1,154 @@
|
|||||||
# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2026.2.25"
|
version = "2026.4.22"
|
||||||
description = "Python package for providing Mozilla's CA Bundle."
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"},
|
{file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"},
|
||||||
{file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"},
|
{file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "charset-normalizer"
|
name = "charset-normalizer"
|
||||||
version = "3.4.6"
|
version = "3.4.7"
|
||||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-win32.whl", hash = "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"},
|
||||||
{file = "charset_normalizer-3.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058"},
|
{file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"},
|
||||||
{file = "charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316"},
|
{file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"},
|
||||||
{file = "charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4"},
|
{file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"},
|
||||||
{file = "charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f"},
|
{file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"},
|
||||||
{file = "charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c"},
|
{file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:659a1e1b500fac8f2779dd9e1570464e012f43e580371470b45277a27baa7532"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f61aa92e4aad0be58eb6eb4e0c21acf32cf8065f4b2cae5665da756c4ceef982"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f50498891691e0864dc3da965f340fada0771f6142a378083dc4608f4ea513e2"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf625105bb9eef28a56a943fec8c8a98aeb80e7d7db99bd3c388137e6eb2d237"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2bd9d128ef93637a5d7a6af25363cf5dec3fa21cf80e68055aad627f280e8afa"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:d08ec48f0a1c48d75d0356cea971921848fb620fdeba805b28f937e90691209f"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ed80ff870ca6de33f4d953fda4d55654b9a2b340ff39ab32fa3adbcd718f264"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f98059e4fcd3e3e4e2d632b7cf81c2faae96c43c60b569e9c621468082f1d104"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:ab30e5e3e706e3063bc6de96b118688cb10396b70bb9864a430f67df98c61ecc"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:d5f5d1e9def3405f60e3ca8232d56f35c98fb7bf581efcc60051ebf53cb8b611"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:461598cd852bfa5a61b09cae2b1c02e2efcd166ee5516e243d540ac24bfa68a7"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:71be7e0e01753a89cf024abf7ecb6bca2c81738ead80d43004d9b5e3f1244e64"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:df01808ee470038c3f8dc4f48620df7225c49c2d6639e38f96e6d6ac6e6f7b0e"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-win32.whl", hash = "sha256:69dd852c2f0ad631b8b60cfbe25a28c0058a894de5abb566619c205ce0550eae"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"},
|
||||||
{file = "charset_normalizer-3.4.6-cp38-cp38-win_amd64.whl", hash = "sha256:517ad0e93394ac532745129ceabdf2696b609ec9f87863d337140317ebce1c14"},
|
{file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-win32.whl", hash = "sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-win_amd64.whl", hash = "sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"},
|
||||||
{file = "charset_normalizer-3.4.6-cp39-cp39-win_arm64.whl", hash = "sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8"},
|
{file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"},
|
||||||
{file = "charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69"},
|
{file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"},
|
||||||
{file = "charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6"},
|
{file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -166,18 +166,18 @@ markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\"",
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.11"
|
version = "3.15"
|
||||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"},
|
{file = "idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8"},
|
||||||
{file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"},
|
{file = "idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
@@ -193,103 +193,103 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librt"
|
name = "librt"
|
||||||
version = "0.8.1"
|
version = "0.11.0"
|
||||||
description = "Mypyc runtime library"
|
description = "Mypyc runtime library"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
groups = ["dev"]
|
groups = ["dev"]
|
||||||
markers = "platform_python_implementation != \"PyPy\""
|
markers = "platform_python_implementation != \"PyPy\""
|
||||||
files = [
|
files = [
|
||||||
{file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"},
|
{file = "librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"},
|
{file = "librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6"},
|
{file = "librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0"},
|
{file = "librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b"},
|
{file = "librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6"},
|
{file = "librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71"},
|
{file = "librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7"},
|
{file = "librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05"},
|
{file = "librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891"},
|
{file = "librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7"},
|
{file = "librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89"},
|
||||||
{file = "librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2"},
|
{file = "librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd"},
|
{file = "librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965"},
|
{file = "librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da"},
|
{file = "librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0"},
|
{file = "librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e"},
|
{file = "librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3"},
|
{file = "librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac"},
|
{file = "librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596"},
|
{file = "librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99"},
|
{file = "librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe"},
|
{file = "librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb"},
|
{file = "librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b"},
|
{file = "librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73"},
|
||||||
{file = "librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9"},
|
{file = "librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a"},
|
{file = "librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9"},
|
{file = "librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb"},
|
{file = "librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d"},
|
{file = "librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7"},
|
{file = "librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440"},
|
{file = "librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9"},
|
{file = "librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972"},
|
{file = "librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921"},
|
{file = "librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0"},
|
{file = "librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a"},
|
{file = "librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444"},
|
{file = "librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9"},
|
||||||
{file = "librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d"},
|
{file = "librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35"},
|
{file = "librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583"},
|
{file = "librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c"},
|
{file = "librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04"},
|
{file = "librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363"},
|
{file = "librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0"},
|
{file = "librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012"},
|
{file = "librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb"},
|
{file = "librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b"},
|
{file = "librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d"},
|
{file = "librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a"},
|
{file = "librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79"},
|
{file = "librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47"},
|
||||||
{file = "librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0"},
|
{file = "librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f"},
|
{file = "librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c"},
|
{file = "librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc"},
|
{file = "librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c"},
|
{file = "librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3"},
|
{file = "librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14"},
|
{file = "librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7"},
|
{file = "librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6"},
|
{file = "librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071"},
|
{file = "librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78"},
|
{file = "librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023"},
|
{file = "librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730"},
|
{file = "librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7"},
|
||||||
{file = "librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3"},
|
{file = "librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1"},
|
{file = "librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee"},
|
{file = "librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7"},
|
{file = "librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040"},
|
{file = "librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e"},
|
{file = "librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732"},
|
{file = "librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624"},
|
{file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4"},
|
{file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382"},
|
{file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994"},
|
{file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a"},
|
{file = "librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4"},
|
{file = "librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c"},
|
||||||
{file = "librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61"},
|
{file = "librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3dff3d3ca8db20e783b1bc7de49c0a2ab0b8387f31236d6a026597d07fcd68ac"},
|
{file = "librt-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6bd72d903911d995ab666dbd1871f8b1e80925a699af8063fbf50053329fb05f"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08eec3a1fc435f0d09c87b6bf1ec798986a3544f446b864e4099633a56fcd9ed"},
|
{file = "librt-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ef69ac715f3cd8e5cd252cb2aebfa72c015492aacc339d5d7bf8fef3c62c677"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3f0a41487fd5fad7e760b9e8a90e251e27c2816fbc2cff36a22a0e6bcbbd9dd"},
|
{file = "librt-0.11.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:624a40c4a4ad7773315c287276cd024509b2c66ff5904f504bfc08d2c70293ab"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bacdb58d9939d95cc557b4dbaa86527c9db2ac1ed76a18bc8d26f6dc8647d851"},
|
{file = "librt-0.11.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:41dc19fe150b69716c8ece4f76773a9e8813fe3e35e032a58b4d46423fb8d7c0"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d7ab1f01aa753188605b09a51faa44a3327400b00b8cce424c71910fc0a128"},
|
{file = "librt-0.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e8bd98ea9c47ae90b319a087ab28dac493f1ffbc1ecd1f28fcdbf3b7e1108d1"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4998009e7cb9e896569f4be7004f09d0ed70d386fa99d42b6d363f6d200501ac"},
|
{file = "librt-0.11.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84308fc49423ce6475d1c5d1985cd69a8ca9f0325fc7d5f81bb690a3f3625d4e"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2cc68eeeef5e906839c7bb0815748b5b0a974ec27125beefc0f942715785b551"},
|
{file = "librt-0.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ff0fbaf5f44a21beeb0110f2ab64f45135a9536a834b79c0d1ef018f2786bbfa"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0bf69d79a23f4f40b8673a947a234baeeb133b5078b483b7297c5916539cf5d5"},
|
{file = "librt-0.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9c028a9442a18e266955d364ce42259136e79a7ba14d773e0d778d5f70cd56f1"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:22b46eabd76c1986ee7d231b0765ad387d7673bbd996aa0d0d054b38ac65d8f6"},
|
{file = "librt-0.11.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9f1692105a02bcf853f355032a5fdc5494358ef83d8fd22d16de375c85cec3f5"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:237796479f4d0637d6b9cbcb926ff424a97735e68ade6facf402df4ec93375ed"},
|
{file = "librt-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a80a71e1fda83cc752a9141e87aae7fef279538597564d670e9ce513f286192"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-win32.whl", hash = "sha256:4beb04b8c66c6ae62f8c1e0b2f097c1ebad9295c929a8d5286c05eae7c2fc7dc"},
|
{file = "librt-0.11.0-cp39-cp39-win32.whl", hash = "sha256:140695816ddf3c86eb972981a26f35efd871c44b0c3aed44c8cd01749386617f"},
|
||||||
{file = "librt-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:64548cde61b692dc0dc379f4b5f59a2f582c2ebe7890d09c1ae3b9e66fa015b7"},
|
{file = "librt-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f7ff819c197fc30473190a12c2856f325ac90aabfccbeb2072d28cc2e234e3"},
|
||||||
{file = "librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73"},
|
{file = "librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -313,63 +313,70 @@ dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; pytho
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.19.1"
|
version = "1.20.2"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.10"
|
||||||
groups = ["dev"]
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"},
|
{file = "mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4"},
|
||||||
{file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"},
|
{file = "mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997"},
|
||||||
{file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"},
|
{file = "mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14"},
|
||||||
{file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"},
|
{file = "mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99"},
|
||||||
{file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"},
|
{file = "mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c"},
|
||||||
{file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"},
|
{file = "mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd"},
|
||||||
{file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"},
|
{file = "mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2"},
|
||||||
{file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"},
|
{file = "mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c"},
|
||||||
{file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"},
|
{file = "mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3"},
|
||||||
{file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"},
|
{file = "mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254"},
|
||||||
{file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"},
|
{file = "mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98"},
|
||||||
{file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"},
|
{file = "mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac"},
|
||||||
{file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"},
|
{file = "mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67"},
|
||||||
{file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"},
|
{file = "mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100"},
|
||||||
{file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"},
|
{file = "mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b"},
|
||||||
{file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"},
|
{file = "mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4"},
|
||||||
{file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"},
|
{file = "mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6"},
|
||||||
{file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"},
|
{file = "mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066"},
|
||||||
{file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"},
|
{file = "mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102"},
|
||||||
{file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"},
|
{file = "mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9"},
|
||||||
{file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"},
|
{file = "mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58"},
|
||||||
{file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"},
|
{file = "mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026"},
|
||||||
{file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"},
|
{file = "mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943"},
|
||||||
{file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"},
|
{file = "mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517"},
|
||||||
{file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"},
|
{file = "mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15"},
|
||||||
{file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"},
|
{file = "mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee"},
|
||||||
{file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"},
|
{file = "mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f"},
|
||||||
{file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"},
|
{file = "mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330"},
|
||||||
{file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"},
|
{file = "mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30"},
|
||||||
{file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"},
|
{file = "mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924"},
|
||||||
{file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"},
|
{file = "mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb"},
|
||||||
{file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"},
|
{file = "mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc"},
|
||||||
{file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"},
|
{file = "mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558"},
|
||||||
{file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"},
|
{file = "mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8"},
|
||||||
{file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"},
|
{file = "mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3"},
|
||||||
{file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"},
|
{file = "mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609"},
|
||||||
{file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"},
|
{file = "mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2"},
|
||||||
{file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"},
|
{file = "mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c"},
|
||||||
|
{file = "mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744"},
|
||||||
|
{file = "mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6"},
|
||||||
|
{file = "mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec"},
|
||||||
|
{file = "mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382"},
|
||||||
|
{file = "mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563"},
|
||||||
|
{file = "mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""}
|
librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""}
|
||||||
mypy_extensions = ">=1.0.0"
|
mypy_extensions = ">=1.0.0"
|
||||||
pathspec = ">=0.9.0"
|
pathspec = ">=1.0.0"
|
||||||
typing_extensions = ">=4.6.0"
|
typing_extensions = {version = ">=4.6.0", markers = "python_version < \"3.15\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dmypy = ["psutil (>=4.0)"]
|
dmypy = ["psutil (>=4.0)"]
|
||||||
faster-cache = ["orjson"]
|
faster-cache = ["orjson"]
|
||||||
install-types = ["pip"]
|
install-types = ["pip"]
|
||||||
mypyc = ["setuptools (>=50)"]
|
mypyc = ["setuptools (>=50)"]
|
||||||
|
native-parser = ["ast-serialize (>=0.1.1,<1.0.0)"]
|
||||||
reports = ["lxml"]
|
reports = ["lxml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -386,33 +393,32 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "26.0"
|
version = "26.2"
|
||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
groups = ["dev"]
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"},
|
{file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"},
|
||||||
{file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
|
{file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
version = "1.0.4"
|
version = "1.1.1"
|
||||||
description = "Utility library for gitignore style pattern matching of file paths."
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
groups = ["dev"]
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"},
|
{file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"},
|
||||||
{file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"},
|
{file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
hyperscan = ["hyperscan (>=0.7)"]
|
hyperscan = ["hyperscan (>=0.7)"]
|
||||||
optional = ["typing-extensions (>=4)"]
|
optional = ["typing-extensions (>=4)"]
|
||||||
re2 = ["google-re2 (>=1.1)"]
|
re2 = ["google-re2 (>=1.1)"]
|
||||||
tests = ["pytest (>=9)", "typing-extensions (>=4.15)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
@@ -432,14 +438,14 @@ testing = ["coverage", "pytest", "pytest-benchmark"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.19.2"
|
version = "2.20.0"
|
||||||
description = "Pygments is a syntax highlighting package written in Python."
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
groups = ["dev"]
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
|
{file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"},
|
||||||
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
|
{file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -447,71 +453,71 @@ windows-terminal = ["colorama (>=0.4.6)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyside6"
|
name = "pyside6"
|
||||||
version = "6.11.0"
|
version = "6.11.1"
|
||||||
description = "Python bindings for the Qt cross-platform application and UI framework"
|
description = "Python bindings for the Qt cross-platform application and UI framework"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<3.15,>=3.10"
|
python-versions = "<3.15,>=3.10"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pyside6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1f2735dc4f2bd4ec452ae50502c8a22128bba0aced35358a2bbc58384b820c6f"},
|
{file = "pyside6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:537682c3b7530817203e667c1f5a2f00486b37bf52c52eeab438544c7a0917f6"},
|
||||||
{file = "pyside6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c642e2d25704ca746fd37f56feacf25c5aecc4cd40bef23d18eec81f87d9dc00"},
|
{file = "pyside6-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b1fc521ba2bb5109425ab8add06bddbdd524abcad06cfa012cc39a22a189feb2"},
|
||||||
{file = "pyside6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:267b344c73580ac938ca63c611881fb42a3922ebfe043e271005f4f06c372c4e"},
|
{file = "pyside6-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:75f0005c3eb95c07cfb65522ec50d0815ac007a96482c21dc3cb4b4c04895d84"},
|
||||||
{file = "pyside6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:9092cb002ca43c64006afb2e0d0f6f51aef17aa737c33a45e502326a081ddcbc"},
|
{file = "pyside6-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:0968877ab1fb4ef3587a284da6fe05e8647ada56a6a3750b6395188e01f4aba6"},
|
||||||
{file = "pyside6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:b15f39acc2b8f46251a630acad0d97f9a0a0461f2baffcd66d7adfada8eb641e"},
|
{file = "pyside6-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:acee467cb5f256cc47ebb9d815a054c1d8416da380c191b247a76d164aa3f805"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
PySide6_Addons = "6.11.0"
|
PySide6_Addons = "6.11.1"
|
||||||
PySide6_Essentials = "6.11.0"
|
PySide6_Essentials = "6.11.1"
|
||||||
shiboken6 = "6.11.0"
|
shiboken6 = "6.11.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyside6-addons"
|
name = "pyside6-addons"
|
||||||
version = "6.11.0"
|
version = "6.11.1"
|
||||||
description = "Python bindings for the Qt cross-platform application and UI framework (Addons)"
|
description = "Python bindings for the Qt cross-platform application and UI framework (Addons)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<3.15,>=3.10"
|
python-versions = "<3.15,>=3.10"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pyside6_addons-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d5eaa4643302e3a0fa94c5766234bee4073d7d5ab9c2b7fd222692a176faf182"},
|
{file = "pyside6_addons-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:54733c77f789bef5f03c6aff4ad3bec8b2eff021f0cfcbc53d5e6c250ded24f9"},
|
||||||
{file = "pyside6_addons-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ac6fe3d4ef4497dde3efc5e896b0acd53ff6c93be4bf485f045690f919419f35"},
|
{file = "pyside6_addons-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6c65fbd73a512d6f72cda8d8277444a85a34dc99dd1dae9c21d35b8671bb1f"},
|
||||||
{file = "pyside6_addons-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8ffb40222456078930816ebcac2f2511716d2acbc11716dd5acc5c365179a753"},
|
{file = "pyside6_addons-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:bf1c6c4e954e5eba3d2a7c661ad4b9689e8f09c7f4a16bdf29713371d11af993"},
|
||||||
{file = "pyside6_addons-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:413e6121c24f5ffdce376298059eddecff74aa6d638e94e0f6015b33d29b889e"},
|
{file = "pyside6_addons-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:0d13c4dfd671b050a48e4f8d8ddc724b7248f9c0437e7fc47fdf316278572923"},
|
||||||
{file = "pyside6_addons-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:aaaee83385977a0fe134b2f4fbfb92b45a880d5b656e4d90a708eef10b1b6de8"},
|
{file = "pyside6_addons-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:3494f480dee92f415be2f2d989c0b3f4755ac332b28045cbf4ba0f5c5a22ba37"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
PySide6_Essentials = "6.11.0"
|
PySide6_Essentials = "6.11.1"
|
||||||
shiboken6 = "6.11.0"
|
shiboken6 = "6.11.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyside6-essentials"
|
name = "pyside6-essentials"
|
||||||
version = "6.11.0"
|
version = "6.11.1"
|
||||||
description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)"
|
description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<3.15,>=3.10"
|
python-versions = "<3.15,>=3.10"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pyside6_essentials-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:85d6ca87ef35fa6565d385ede72ae48420dd3f63113929d10fc800f6b0360e01"},
|
{file = "pyside6_essentials-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:228de53c2bc26b07e5021fbe3614fc44ca08e4dab9999af08c2b389d2c239957"},
|
||||||
{file = "pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:dc20e7afd5fc6fe51297db91cef997ce60844be578f7a49fc61b7ab9657a8849"},
|
{file = "pyside6_essentials-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e3ef7027b41e4e55fadb56e3b3257dc8ee92154b639fe67fc4c8e05e9d976c60"},
|
||||||
{file = "pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:4854cb0a1b061e7a576d8fb7bb7cf9f49540d558b1acb7df0742a7afefe61e4e"},
|
{file = "pyside6_essentials-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a039b6da68a3a4b9d243217b2b98d475eed3f617159ef6be925badab53c11b0d"},
|
||||||
{file = "pyside6_essentials-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:3b3362882ad9389357a80504e600180006a957731fec05786fced7b038461fdf"},
|
{file = "pyside6_essentials-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:63311bd48e32c584599ab04b9ef7c324082374cd2c9fa533f978fb893bb47e40"},
|
||||||
{file = "pyside6_essentials-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:81ca603dbf21bc39f89bb42db215c25ebe0c879a1a4c387625c321d2730ec187"},
|
{file = "pyside6_essentials-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:11253ea52aabecefe9febddbbe78b43a824129e3af1cec98431028fba7fa954f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
shiboken6 = "6.11.0"
|
shiboken6 = "6.11.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "9.0.2"
|
version = "9.0.3"
|
||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
groups = ["dev"]
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"},
|
{file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"},
|
||||||
{file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"},
|
{file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -559,14 +565,14 @@ cli = ["click (>=5.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.33.0"
|
version = "2.34.2"
|
||||||
description = "Python HTTP for Humans."
|
description = "Python HTTP for Humans."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b"},
|
{file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"},
|
||||||
{file = "requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652"},
|
{file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -577,50 +583,49 @@ urllib3 = ">=1.26,<3"
|
|||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||||
test = ["PySocks (>=1.5.6,!=1.5.7)", "pytest (>=3)", "pytest-cov", "pytest-httpbin (==2.1.0)", "pytest-mock", "pytest-xdist"]
|
|
||||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"]
|
use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.15.7"
|
version = "0.15.13"
|
||||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
groups = ["dev"]
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e"},
|
{file = "ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8"},
|
||||||
{file = "ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477"},
|
{file = "ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7"},
|
||||||
{file = "ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e"},
|
{file = "ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629"},
|
||||||
{file = "ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf"},
|
{file = "ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5"},
|
||||||
{file = "ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85"},
|
{file = "ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22"},
|
||||||
{file = "ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0"},
|
{file = "ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9"},
|
||||||
{file = "ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912"},
|
{file = "ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55"},
|
||||||
{file = "ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036"},
|
{file = "ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6"},
|
||||||
{file = "ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5"},
|
{file = "ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca"},
|
||||||
{file = "ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12"},
|
{file = "ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd"},
|
||||||
{file = "ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c"},
|
{file = "ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6"},
|
||||||
{file = "ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4"},
|
{file = "ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51"},
|
||||||
{file = "ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d"},
|
{file = "ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2"},
|
||||||
{file = "ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580"},
|
{file = "ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b"},
|
||||||
{file = "ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de"},
|
{file = "ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41"},
|
||||||
{file = "ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1"},
|
{file = "ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4"},
|
||||||
{file = "ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2"},
|
{file = "ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21"},
|
||||||
{file = "ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac"},
|
{file = "ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shiboken6"
|
name = "shiboken6"
|
||||||
version = "6.11.0"
|
version = "6.11.1"
|
||||||
description = "Python/C++ bindings helper module"
|
description = "Python/C++ bindings helper module"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<3.15,>=3.10"
|
python-versions = "<3.15,>=3.10"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "shiboken6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d88e8a1eb705f2b9ad21db08a61ae1dc0c773e5cd86a069de0754c4cf1f9b43b"},
|
{file = "shiboken6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1a16867f103ef1c662a5f09dfed03273a9f81688b174555162c58e83650a3f02"},
|
||||||
{file = "shiboken6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad54e64f8192ddbdff0c54ac82b89edcd62ed623f502ea21c960541d19514053"},
|
{file = "shiboken6-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9a8bccfafc8805254cabcfa1edfaf55cd52889f4998c91ad0d9a4433fb1bcdbe"},
|
||||||
{file = "shiboken6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a10dc7718104ea2dc15d5b0b96909b77162ce1c76fcc6968e6df692b947a00e9"},
|
{file = "shiboken6-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:1bd2f4314414df2d122d9f646e03b731bc6d6b5f77a5f53f99a4fe4e97d84e6f"},
|
||||||
{file = "shiboken6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:483ff78a73c7b3189ca924abc694318084f078bcfeaffa68e32024ff2d025ee1"},
|
{file = "shiboken6-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:c2c6863aa80ec18c0f82cea3417837b279cdc60024ac17123461dc9042577df7"},
|
||||||
{file = "shiboken6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:3bd76cf56105ab2d62ecaff630366f11264f69b88d488f10f048da9a065781f4"},
|
{file = "shiboken6-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:7c8d9af17db4495d4fa5b1c393f218311c4855546b9dfa6a0bd21bcd66b55e9d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -647,14 +652,14 @@ telegram = ["requests"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-requests"
|
name = "types-requests"
|
||||||
version = "2.32.4.20260324"
|
version = "2.33.0.20260513"
|
||||||
description = "Typing stubs for requests"
|
description = "Typing stubs for requests"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
groups = ["dev"]
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "types_requests-2.32.4.20260324-py3-none-any.whl", hash = "sha256:f83ef2deb284fe99a249b8b0b0a3e4b9809e01ff456063c4df0aac7670c07ab9"},
|
{file = "types_requests-2.33.0.20260513-py3-none-any.whl", hash = "sha256:d5a965f9d18b6e06b72039a69565de9027e58f36a7f709857da747fbe7521122"},
|
||||||
{file = "types_requests-2.32.4.20260324.tar.gz", hash = "sha256:33a2a9ccb1de7d4e4da36e347622c35418f6761269014cc32857acabd5df739e"},
|
{file = "types_requests-2.33.0.20260513.tar.gz", hash = "sha256:bd845450e954e751373d5d33526742592f298808a3ee3bda7e858e46b839b57f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -674,14 +679,14 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "2.6.3"
|
version = "2.7.0"
|
||||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.10"
|
||||||
groups = ["main", "dev"]
|
groups = ["main", "dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"},
|
{file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"},
|
||||||
{file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"},
|
{file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "gogupdater"
|
name = "gogupdater"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Jan Doubravský",email = "jan.doubravsky@gmail.com"}
|
{name = "Jan Doubravský",email = "jan.doubravsky@gmail.com"}
|
||||||
|
|||||||
+80
-7
@@ -1,11 +1,14 @@
|
|||||||
"""GOG API client for fetching game library and installer info."""
|
"""GOG API client for fetching game library and installer info."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from urllib.parse import unquote, urlparse
|
from urllib.parse import unquote, urlparse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from src.auth import AuthManager
|
from src.auth import AuthManager
|
||||||
|
from src.constants import APP_NAME, VERSION
|
||||||
from src.models import BonusContent, InstallerInfo, InstallerPlatform, InstallerType, OwnedGame
|
from src.models import BonusContent, InstallerInfo, InstallerPlatform, InstallerType, OwnedGame
|
||||||
|
|
||||||
GOG_API = "https://api.gog.com"
|
GOG_API = "https://api.gog.com"
|
||||||
@@ -15,10 +18,27 @@ GOG_EMBED = "https://embed.gog.com"
|
|||||||
class GogApi:
|
class GogApi:
|
||||||
"""Handles communication with the GOG API."""
|
"""Handles communication with the GOG API."""
|
||||||
|
|
||||||
|
PRODUCT_FETCH_WORKERS = 16
|
||||||
|
|
||||||
def __init__(self, auth: AuthManager) -> None:
|
def __init__(self, auth: AuthManager) -> None:
|
||||||
self.auth = auth
|
self.auth = auth
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.session.headers.update({"User-Agent": "GOGUpdater/0.1"})
|
self.session.headers.update({"User-Agent": f"{APP_NAME}/{VERSION}"})
|
||||||
|
# Larger connection pool so concurrent product fetches don't block each other.
|
||||||
|
adapter = requests.adapters.HTTPAdapter(
|
||||||
|
pool_connections=self.PRODUCT_FETCH_WORKERS,
|
||||||
|
pool_maxsize=self.PRODUCT_FETCH_WORKERS,
|
||||||
|
)
|
||||||
|
self.session.mount("https://", adapter)
|
||||||
|
self.session.mount("http://", adapter)
|
||||||
|
# Caches to avoid refetching the same data repeatedly within a session.
|
||||||
|
self._product_cache: dict[str, dict] = {}
|
||||||
|
self._owned_ids_cache: list[int] | None = None
|
||||||
|
|
||||||
|
def clear_cache(self) -> None:
|
||||||
|
"""Drop cached product info and owned IDs (call before a fresh refresh)."""
|
||||||
|
self._product_cache.clear()
|
||||||
|
self._owned_ids_cache = None
|
||||||
|
|
||||||
def _ensure_auth(self) -> bool:
|
def _ensure_auth(self) -> bool:
|
||||||
token = self.auth.access_token
|
token = self.auth.access_token
|
||||||
@@ -30,17 +50,25 @@ class GogApi:
|
|||||||
|
|
||||||
def get_owned_game_ids(self) -> list[int]:
|
def get_owned_game_ids(self) -> list[int]:
|
||||||
"""Return list of owned game IDs."""
|
"""Return list of owned game IDs."""
|
||||||
|
if self._owned_ids_cache is not None:
|
||||||
|
logger.debug(f"get_owned_game_ids: cache hit ({len(self._owned_ids_cache)} ids)")
|
||||||
|
return self._owned_ids_cache
|
||||||
if not self._ensure_auth():
|
if not self._ensure_auth():
|
||||||
|
logger.error("get_owned_game_ids: not authenticated")
|
||||||
return []
|
return []
|
||||||
|
logger.debug(f"get_owned_game_ids: requesting {GOG_EMBED}/user/data/games")
|
||||||
try:
|
try:
|
||||||
response = self.session.get(f"{GOG_EMBED}/user/data/games", timeout=15)
|
response = self.session.get(f"{GOG_EMBED}/user/data/games", timeout=15)
|
||||||
except (requests.ConnectionError, requests.Timeout):
|
except (requests.ConnectionError, requests.Timeout) as e:
|
||||||
logger.error("Failed to fetch owned games — network error")
|
logger.error(f"Failed to fetch owned games — network error: {e}")
|
||||||
return []
|
return []
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
logger.error(f"Failed to fetch owned games — HTTP {response.status_code}")
|
logger.error(f"Failed to fetch owned games — HTTP {response.status_code}")
|
||||||
return []
|
return []
|
||||||
return response.json().get("owned", [])
|
owned = response.json().get("owned", [])
|
||||||
|
logger.info(f"get_owned_game_ids: received {len(owned)} owned IDs")
|
||||||
|
self._owned_ids_cache = owned
|
||||||
|
return owned
|
||||||
|
|
||||||
def get_game_details(self, game_id: int | str) -> dict | None:
|
def get_game_details(self, game_id: int | str) -> dict | None:
|
||||||
"""Fetch game details including download links."""
|
"""Fetch game details including download links."""
|
||||||
@@ -61,7 +89,12 @@ class GogApi:
|
|||||||
|
|
||||||
def get_product_info(self, game_id: int | str) -> dict | None:
|
def get_product_info(self, game_id: int | str) -> dict | None:
|
||||||
"""Fetch product info with downloads and DLC expansions."""
|
"""Fetch product info with downloads and DLC expansions."""
|
||||||
|
key = str(game_id)
|
||||||
|
cached = self._product_cache.get(key)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
if not self._ensure_auth():
|
if not self._ensure_auth():
|
||||||
|
logger.error(f"get_product_info({game_id}): not authenticated")
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
@@ -69,13 +102,53 @@ class GogApi:
|
|||||||
params={"expand": "downloads,expanded_dlcs"},
|
params={"expand": "downloads,expanded_dlcs"},
|
||||||
timeout=15,
|
timeout=15,
|
||||||
)
|
)
|
||||||
except (requests.ConnectionError, requests.Timeout):
|
except (requests.ConnectionError, requests.Timeout) as e:
|
||||||
logger.error(f"Failed to fetch product info for {game_id} — network error")
|
logger.error(f"Failed to fetch product info for {game_id} — network error: {e}")
|
||||||
return None
|
return None
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
logger.error(f"Failed to fetch product info for {game_id} — HTTP {response.status_code}")
|
logger.error(f"Failed to fetch product info for {game_id} — HTTP {response.status_code}")
|
||||||
return None
|
return None
|
||||||
return response.json()
|
product = response.json()
|
||||||
|
self._product_cache[key] = product
|
||||||
|
logger.debug(f"get_product_info({game_id}): OK")
|
||||||
|
return product
|
||||||
|
|
||||||
|
def get_products(
|
||||||
|
self,
|
||||||
|
game_ids: list[int] | list[str],
|
||||||
|
progress: Callable[[int, int], None] | None = None,
|
||||||
|
) -> dict[str, dict]:
|
||||||
|
"""Fetch product info for many games in parallel.
|
||||||
|
|
||||||
|
Returns a dict keyed by str(game_id); games whose info could not be
|
||||||
|
fetched are simply absent. `progress(done, total)` is called as each
|
||||||
|
request completes (may be invoked from worker threads).
|
||||||
|
"""
|
||||||
|
total = len(game_ids)
|
||||||
|
results: dict[str, dict] = {}
|
||||||
|
if not total:
|
||||||
|
return results
|
||||||
|
|
||||||
|
logger.info(f"get_products: fetching {total} products with {self.PRODUCT_FETCH_WORKERS} workers")
|
||||||
|
done = 0
|
||||||
|
with ThreadPoolExecutor(max_workers=self.PRODUCT_FETCH_WORKERS) as executor:
|
||||||
|
futures = {executor.submit(self.get_product_info, gid): gid for gid in game_ids}
|
||||||
|
for future in as_completed(futures):
|
||||||
|
gid = futures[future]
|
||||||
|
try:
|
||||||
|
product = future.result()
|
||||||
|
except Exception as e: # defensive — keep going if one game fails
|
||||||
|
logger.error(f"Error fetching product {gid}: {e}")
|
||||||
|
product = None
|
||||||
|
if product is not None:
|
||||||
|
results[str(gid)] = product
|
||||||
|
done += 1
|
||||||
|
if done % 10 == 0 or done == total:
|
||||||
|
logger.info(f"get_products: {done}/{total} fetched")
|
||||||
|
if progress:
|
||||||
|
progress(done, total)
|
||||||
|
logger.info(f"get_products: done, {len(results)}/{total} succeeded")
|
||||||
|
return results
|
||||||
|
|
||||||
def get_owned_games(self) -> list[OwnedGame]:
|
def get_owned_games(self) -> list[OwnedGame]:
|
||||||
"""Fetch list of owned games with titles."""
|
"""Fetch list of owned games with titles."""
|
||||||
|
|||||||
+5
-3
@@ -7,6 +7,9 @@ from pathlib import Path
|
|||||||
import requests
|
import requests
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from src.constants import APP_NAME, VERSION
|
||||||
|
from src.fileutil import atomic_write_text
|
||||||
|
|
||||||
GOG_AUTH_URL = "https://auth.gog.com"
|
GOG_AUTH_URL = "https://auth.gog.com"
|
||||||
CLIENT_ID = "46899977096215655"
|
CLIENT_ID = "46899977096215655"
|
||||||
CLIENT_SECRET = "9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9"
|
CLIENT_SECRET = "9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9"
|
||||||
@@ -27,7 +30,7 @@ class AuthManager:
|
|||||||
self.auth_file = config_dir / "auth.json"
|
self.auth_file = config_dir / "auth.json"
|
||||||
self.credentials: dict = {}
|
self.credentials: dict = {}
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.session.headers.update({"User-Agent": "GOGUpdater/0.1"})
|
self.session.headers.update({"User-Agent": f"{APP_NAME}/{VERSION}"})
|
||||||
self._load()
|
self._load()
|
||||||
|
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
@@ -39,8 +42,7 @@ class AuthManager:
|
|||||||
self.credentials = {}
|
self.credentials = {}
|
||||||
|
|
||||||
def _save(self) -> None:
|
def _save(self) -> None:
|
||||||
self.config_dir.mkdir(parents=True, exist_ok=True)
|
atomic_write_text(self.auth_file, json.dumps(self.credentials, indent=2))
|
||||||
self.auth_file.write_text(json.dumps(self.credentials, indent=2), encoding="utf-8")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_logged_in(self) -> bool:
|
def is_logged_in(self) -> bool:
|
||||||
|
|||||||
+10
-11
@@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from src.fileutil import atomic_write_text
|
||||||
from src.models import DownloadedInstaller, GameRecord, GameSettings, InstallerType, LANGUAGE_NAMES, PruneStrategy, language_folder_name
|
from src.models import DownloadedInstaller, GameRecord, GameSettings, InstallerType, LANGUAGE_NAMES, PruneStrategy, language_folder_name
|
||||||
|
|
||||||
DEFAULT_CONFIG_DIR = Path.home() / ".config" / "gogupdater"
|
DEFAULT_CONFIG_DIR = Path.home() / ".config" / "gogupdater"
|
||||||
@@ -54,7 +55,6 @@ class AppConfig:
|
|||||||
logger.warning("Failed to read config.json, using defaults")
|
logger.warning("Failed to read config.json, using defaults")
|
||||||
|
|
||||||
def save(self) -> None:
|
def save(self) -> None:
|
||||||
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
data = {
|
data = {
|
||||||
"windows_path": self.windows_path,
|
"windows_path": self.windows_path,
|
||||||
"linux_path": self.linux_path,
|
"linux_path": self.linux_path,
|
||||||
@@ -71,7 +71,7 @@ class AppConfig:
|
|||||||
"prune_keep_count": self.prune_keep_count,
|
"prune_keep_count": self.prune_keep_count,
|
||||||
"prune_strategy": self.prune_strategy.value,
|
"prune_strategy": self.prune_strategy.value,
|
||||||
}
|
}
|
||||||
self.config_file.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
atomic_write_text(self.config_file, json.dumps(data, indent=2))
|
||||||
|
|
||||||
def is_game_managed(self, game_id: str) -> bool:
|
def is_game_managed(self, game_id: str) -> bool:
|
||||||
return game_id in self.managed_game_ids
|
return game_id in self.managed_game_ids
|
||||||
@@ -140,9 +140,8 @@ class MetadataStore:
|
|||||||
logger.warning(f"Failed to read {self.metadata_file}, starting fresh")
|
logger.warning(f"Failed to read {self.metadata_file}, starting fresh")
|
||||||
|
|
||||||
def save(self) -> None:
|
def save(self) -> None:
|
||||||
self.base_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
data = {"games": {gid: record.to_dict() for gid, record in self.games.items()}}
|
data = {"games": {gid: record.to_dict() for gid, record in self.games.items()}}
|
||||||
self.metadata_file.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
atomic_write_text(self.metadata_file, json.dumps(data, indent=2))
|
||||||
|
|
||||||
def get_game(self, game_id: str) -> GameRecord | None:
|
def get_game(self, game_id: str) -> GameRecord | None:
|
||||||
return self.games.get(game_id)
|
return self.games.get(game_id)
|
||||||
@@ -300,21 +299,21 @@ class MetadataStore:
|
|||||||
|
|
||||||
return removed
|
return removed
|
||||||
|
|
||||||
def scan_existing_installers(self, title_to_id: dict[str, str]) -> int:
|
def scan_existing_installers(self, title_to_id: dict[str, str]) -> list[str]:
|
||||||
"""Scan directory for existing installers and populate metadata.
|
"""Scan directory for existing installers and populate metadata.
|
||||||
|
|
||||||
Expects structure: base_path/GameName/version/[Language/]installer_file
|
Expects structure: base_path/GameName/version/[Language/]installer_file
|
||||||
title_to_id maps game titles (folder names) to GOG game IDs.
|
title_to_id maps game titles (folder names) to GOG game IDs.
|
||||||
Returns number of games detected.
|
Returns list of game IDs that were newly detected.
|
||||||
"""
|
"""
|
||||||
if not self.base_path.is_dir():
|
if not self.base_path.is_dir():
|
||||||
logger.warning(f"Scan path does not exist: {self.base_path}")
|
logger.warning(f"Scan path does not exist: {self.base_path}")
|
||||||
return 0
|
return []
|
||||||
|
|
||||||
# Reverse language map: folder name -> code
|
# Reverse language map: folder name -> code
|
||||||
lang_name_to_code: dict[str, str] = {v: k for k, v in LANGUAGE_NAMES.items()}
|
lang_name_to_code: dict[str, str] = {v: k for k, v in LANGUAGE_NAMES.items()}
|
||||||
|
|
||||||
detected = 0
|
detected_ids: list[str] = []
|
||||||
for game_dir in sorted(self.base_path.iterdir()):
|
for game_dir in sorted(self.base_path.iterdir()):
|
||||||
if not game_dir.is_dir() or game_dir.name.startswith("."):
|
if not game_dir.is_dir() or game_dir.name.startswith("."):
|
||||||
continue
|
continue
|
||||||
@@ -390,9 +389,9 @@ class MetadataStore:
|
|||||||
installers=installers,
|
installers=installers,
|
||||||
)
|
)
|
||||||
self.games[game_id] = record
|
self.games[game_id] = record
|
||||||
detected += 1
|
detected_ids.append(game_id)
|
||||||
logger.info(f"Scan: detected '{game_name}' with {len(installers)} installer(s), version {latest_version}")
|
logger.info(f"Scan: detected '{game_name}' with {len(installers)} installer(s), version {latest_version}")
|
||||||
|
|
||||||
if detected:
|
if detected_ids:
|
||||||
self.save()
|
self.save()
|
||||||
return detected
|
return detected_ids
|
||||||
|
|||||||
+23
-6
@@ -99,9 +99,18 @@ class InstallerDownloader:
|
|||||||
callback.on_finished(False, f"HTTP {response.status_code}")
|
callback.on_finished(False, f"HTTP {response.status_code}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
total_size = int(response.headers.get("content-length", 0)) + existing_size
|
# Only append if the server honored the Range request (206). A plain 200
|
||||||
downloaded = existing_size
|
# means the body starts from byte 0, so appending would corrupt the file.
|
||||||
mode = "ab" if existing_size > 0 else "wb"
|
if existing_size > 0 and response.status_code == 206:
|
||||||
|
downloaded = existing_size
|
||||||
|
mode = "ab"
|
||||||
|
total_size = int(response.headers.get("content-length", 0)) + existing_size
|
||||||
|
else:
|
||||||
|
if existing_size > 0:
|
||||||
|
logger.warning(f"Server ignored Range for {real_filename}, restarting download")
|
||||||
|
downloaded = 0
|
||||||
|
mode = "wb"
|
||||||
|
total_size = int(response.headers.get("content-length", 0))
|
||||||
|
|
||||||
last_time = time.monotonic()
|
last_time = time.monotonic()
|
||||||
last_downloaded = downloaded
|
last_downloaded = downloaded
|
||||||
@@ -194,9 +203,17 @@ class InstallerDownloader:
|
|||||||
callback.on_finished(False, f"HTTP {response.status_code}")
|
callback.on_finished(False, f"HTTP {response.status_code}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
total_size = int(response.headers.get("content-length", 0)) + existing_size
|
# Only append if the server honored the Range request (206).
|
||||||
downloaded = existing_size
|
if existing_size > 0 and response.status_code == 206:
|
||||||
mode = "ab" if existing_size > 0 else "wb"
|
downloaded = existing_size
|
||||||
|
mode = "ab"
|
||||||
|
total_size = int(response.headers.get("content-length", 0)) + existing_size
|
||||||
|
else:
|
||||||
|
if existing_size > 0:
|
||||||
|
logger.warning(f"Server ignored Range for {real_filename}, restarting download")
|
||||||
|
downloaded = 0
|
||||||
|
mode = "wb"
|
||||||
|
total_size = int(response.headers.get("content-length", 0))
|
||||||
|
|
||||||
last_time = time.monotonic()
|
last_time = time.monotonic()
|
||||||
last_downloaded = downloaded
|
last_downloaded = downloaded
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""Small filesystem helpers."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def atomic_write_text(path: Path, text: str, encoding: str = "utf-8") -> None:
|
||||||
|
"""Write text to a file atomically.
|
||||||
|
|
||||||
|
Writes to a temporary file in the same directory and then renames it over
|
||||||
|
the target. This prevents a crash mid-write from corrupting the existing
|
||||||
|
file (config.json, metadata, auth tokens).
|
||||||
|
"""
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
fd, tmp_name = tempfile.mkstemp(dir=path.parent, prefix=f".{path.name}.", suffix=".tmp")
|
||||||
|
tmp_path = Path(tmp_name)
|
||||||
|
try:
|
||||||
|
with os.fdopen(fd, "w", encoding=encoding) as f:
|
||||||
|
f.write(text)
|
||||||
|
f.flush()
|
||||||
|
os.fsync(f.fileno())
|
||||||
|
os.replace(tmp_path, path)
|
||||||
|
except BaseException:
|
||||||
|
tmp_path.unlink(missing_ok=True)
|
||||||
|
raise
|
||||||
+51
-15
@@ -13,10 +13,14 @@ from PySide6.QtWidgets import (
|
|||||||
)
|
)
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from src.api import GogApi
|
from src.api import GogApi
|
||||||
from src.config import AppConfig
|
from src.config import AppConfig
|
||||||
from src.models import InstallerPlatform
|
from src.models import InstallerPlatform
|
||||||
from src.ui.dialog_game_settings import GameSettingsDialog
|
from src.ui.dialog_game_settings import GameSettingsDialog
|
||||||
|
from src.ui.workers import FetchWorker, run_on_thread
|
||||||
|
|
||||||
COL_TITLE = 0
|
COL_TITLE = 0
|
||||||
COL_ID = 1
|
COL_ID = 1
|
||||||
@@ -44,6 +48,7 @@ class LibraryTab(QWidget):
|
|||||||
self.api = api
|
self.api = api
|
||||||
self.config = config
|
self.config = config
|
||||||
self._updating = False
|
self._updating = False
|
||||||
|
self._active_threads: list = []
|
||||||
# store games_data per platform for re-populating after settings change
|
# store games_data per platform for re-populating after settings change
|
||||||
self._games_data: dict[InstallerPlatform, list[tuple[str, str, list[tuple[str, str]]]]] = {
|
self._games_data: dict[InstallerPlatform, list[tuple[str, str, list[tuple[str, str]]]]] = {
|
||||||
InstallerPlatform.WINDOWS: [],
|
InstallerPlatform.WINDOWS: [],
|
||||||
@@ -81,34 +86,47 @@ class LibraryTab(QWidget):
|
|||||||
self.platform_tabs.setTabVisible(1, bool(self.config.linux_path))
|
self.platform_tabs.setTabVisible(1, bool(self.config.linux_path))
|
||||||
|
|
||||||
def refresh_library(self) -> None:
|
def refresh_library(self) -> None:
|
||||||
"""Fetch owned games from GOG API and populate both platform trees."""
|
"""Fetch owned games from GOG API (in a background thread) and populate trees."""
|
||||||
|
if any(thread.isRunning() for thread, _ in self._active_threads):
|
||||||
|
return # a refresh is already in progress
|
||||||
self.status_label.setText("Loading library...")
|
self.status_label.setText("Loading library...")
|
||||||
self.tree_windows.clear()
|
self.tree_windows.clear()
|
||||||
self.tree_linux.clear()
|
self.tree_linux.clear()
|
||||||
self._update_platform_tab_visibility()
|
self._update_platform_tab_visibility()
|
||||||
logger.info("Refreshing game library")
|
logger.info("Refreshing game library")
|
||||||
|
self.api.clear_cache()
|
||||||
|
|
||||||
# TODO: Run in a thread to avoid blocking the GUI
|
worker = FetchWorker(self._fetch_library_data)
|
||||||
|
worker.result.connect(self._on_library_fetched)
|
||||||
|
worker.failed.connect(self._on_library_failed)
|
||||||
|
worker.progress.connect(self._on_library_progress)
|
||||||
|
run_on_thread(self, worker)
|
||||||
|
logger.debug("refresh_library: worker dispatched")
|
||||||
|
|
||||||
|
def _fetch_library_data(self, progress: Callable[[int, int], None]) -> object:
|
||||||
|
"""Network fetch — runs on a worker thread. Returns None if not logged in."""
|
||||||
|
logger.debug("_fetch_library_data: begin (worker thread)")
|
||||||
owned_ids = self.api.get_owned_game_ids()
|
owned_ids = self.api.get_owned_game_ids()
|
||||||
if not owned_ids:
|
if not owned_ids:
|
||||||
logger.warning("No owned games found or not logged in")
|
logger.warning("_fetch_library_data: no owned IDs returned")
|
||||||
self.status_label.setText("No games found or not logged in.")
|
return None
|
||||||
return
|
|
||||||
|
logger.info(f"Fetching product info for {len(owned_ids)} games (parallel)")
|
||||||
|
products_cache = self.api.get_products(owned_ids, progress=progress)
|
||||||
|
logger.debug(f"_fetch_library_data: got {len(products_cache)} products, building tree data")
|
||||||
|
|
||||||
owned_set = {str(gid) for gid in owned_ids}
|
owned_set = {str(gid) for gid in owned_ids}
|
||||||
dlc_ids: set[str] = set()
|
dlc_ids: set[str] = set()
|
||||||
# games_data shared for all platforms — platform filtering happens in populate
|
|
||||||
games_data: list[tuple[str, str, list[tuple[str, str]], set[str]]] = []
|
|
||||||
# (game_id, title, [(dlc_id, dlc_title)], available_platforms)
|
# (game_id, title, [(dlc_id, dlc_title)], available_platforms)
|
||||||
products_cache: dict[str, dict] = {}
|
games_data: list[tuple[str, str, list[tuple[str, str]], set[str]]] = []
|
||||||
|
|
||||||
for gid in owned_ids:
|
for gid in owned_ids:
|
||||||
product = self.api.get_product_info(gid)
|
key = str(gid)
|
||||||
|
product = products_cache.get(key)
|
||||||
if not product:
|
if not product:
|
||||||
games_data.append((str(gid), f"Game {gid}", [], set()))
|
games_data.append((key, f"Game {gid}", [], set()))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
products_cache[str(gid)] = product
|
|
||||||
title = product.get("title", f"Game {gid}")
|
title = product.get("title", f"Game {gid}")
|
||||||
dlcs: list[tuple[str, str]] = []
|
dlcs: list[tuple[str, str]] = []
|
||||||
for dlc in product.get("expanded_dlcs", []):
|
for dlc in product.get("expanded_dlcs", []):
|
||||||
@@ -117,15 +135,33 @@ class LibraryTab(QWidget):
|
|||||||
dlc_ids.add(dlc_id)
|
dlc_ids.add(dlc_id)
|
||||||
dlcs.append((dlc_id, dlc.get("title", f"DLC {dlc_id}")))
|
dlcs.append((dlc_id, dlc.get("title", f"DLC {dlc_id}")))
|
||||||
|
|
||||||
# Detect which platforms have installers
|
|
||||||
installers = product.get("downloads", {}).get("installers", [])
|
installers = product.get("downloads", {}).get("installers", [])
|
||||||
platforms: set[str] = {inst.get("os", "") for inst in installers}
|
platforms: set[str] = {inst.get("os", "") for inst in installers}
|
||||||
|
games_data.append((key, title, dlcs, platforms))
|
||||||
|
|
||||||
games_data.append((str(gid), title, dlcs, platforms))
|
|
||||||
|
|
||||||
# Filter out DLC top-level entries and sort
|
|
||||||
games_data = [(gid, title, dlcs, plats) for gid, title, dlcs, plats in games_data if gid not in dlc_ids]
|
games_data = [(gid, title, dlcs, plats) for gid, title, dlcs, plats in games_data if gid not in dlc_ids]
|
||||||
games_data.sort(key=lambda x: x[1].lower())
|
games_data.sort(key=lambda x: x[1].lower())
|
||||||
|
return games_data, products_cache, owned_set
|
||||||
|
|
||||||
|
def _on_library_progress(self, done: int, total: int) -> None:
|
||||||
|
self.status_label.setText(f"Loading library {done}/{total}...")
|
||||||
|
|
||||||
|
def _on_library_failed(self, message: str) -> None:
|
||||||
|
logger.error(f"Library refresh failed: {message}")
|
||||||
|
self.status_label.setText("Failed to load library.")
|
||||||
|
|
||||||
|
def _on_library_fetched(self, result: object) -> None:
|
||||||
|
"""Populate trees on the main thread from fetched data."""
|
||||||
|
logger.debug("_on_library_fetched: populating trees (main thread)")
|
||||||
|
if result is None:
|
||||||
|
logger.warning("No owned games found or not logged in")
|
||||||
|
self.status_label.setText("No games found or not logged in.")
|
||||||
|
return
|
||||||
|
|
||||||
|
games_data, products_cache, owned_set = cast(
|
||||||
|
tuple[list[tuple[str, str, list[tuple[str, str]], set[str]]], dict[str, dict], set[str]],
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
|
||||||
self._updating = True
|
self._updating = True
|
||||||
self.tree_windows.setSortingEnabled(False)
|
self.tree_windows.setSortingEnabled(False)
|
||||||
|
|||||||
+19
-5
@@ -18,6 +18,7 @@ from loguru import logger
|
|||||||
|
|
||||||
from src.api import GogApi
|
from src.api import GogApi
|
||||||
from src.config import AppConfig, MetadataStore
|
from src.config import AppConfig, MetadataStore
|
||||||
|
from src.models import sanitize_folder_name
|
||||||
|
|
||||||
|
|
||||||
class SettingsTab(QWidget):
|
class SettingsTab(QWidget):
|
||||||
@@ -135,10 +136,13 @@ class SettingsTab(QWidget):
|
|||||||
self.status_label.setText("Failed to fetch game library.")
|
self.status_label.setText("Failed to fetch game library.")
|
||||||
return
|
return
|
||||||
|
|
||||||
title_to_id: dict[str, str] = {game.title: game.game_id for game in owned_games}
|
# Folders on disk use sanitized titles, so match against sanitized keys.
|
||||||
|
title_to_id: dict[str, str] = {
|
||||||
|
sanitize_folder_name(game.title): game.game_id for game in owned_games
|
||||||
|
}
|
||||||
logger.info(f"Scan: loaded {len(title_to_id)} owned games for matching")
|
logger.info(f"Scan: loaded {len(title_to_id)} owned games for matching")
|
||||||
|
|
||||||
total_detected = 0
|
all_detected_ids: list[str] = []
|
||||||
scanned_paths: list[str] = []
|
scanned_paths: list[str] = []
|
||||||
|
|
||||||
for path in [self.config.windows_path, self.config.linux_path]:
|
for path in [self.config.windows_path, self.config.linux_path]:
|
||||||
@@ -147,13 +151,22 @@ class SettingsTab(QWidget):
|
|||||||
scanned_paths.append(path)
|
scanned_paths.append(path)
|
||||||
self.status_label.setText(f"Scanning {path}...")
|
self.status_label.setText(f"Scanning {path}...")
|
||||||
metadata = MetadataStore(path)
|
metadata = MetadataStore(path)
|
||||||
detected = metadata.scan_existing_installers(title_to_id)
|
detected_ids = metadata.scan_existing_installers(title_to_id)
|
||||||
total_detected += detected
|
all_detected_ids.extend(detected_ids)
|
||||||
|
|
||||||
if not scanned_paths:
|
if not scanned_paths:
|
||||||
self.status_label.setText("No paths configured. Set Windows/Linux paths first.")
|
self.status_label.setText("No paths configured. Set Windows/Linux paths first.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
newly_managed = 0
|
||||||
|
for game_id in all_detected_ids:
|
||||||
|
if not self.config.is_game_managed(game_id):
|
||||||
|
self.config.set_game_managed(game_id, True)
|
||||||
|
newly_managed += 1
|
||||||
|
if newly_managed:
|
||||||
|
self.config.save()
|
||||||
|
|
||||||
|
total_detected = len(all_detected_ids)
|
||||||
msg = f"Scan complete. Detected {total_detected} game(s) with existing installers."
|
msg = f"Scan complete. Detected {total_detected} game(s) with existing installers."
|
||||||
self.status_label.setText(msg)
|
self.status_label.setText(msg)
|
||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
@@ -163,5 +176,6 @@ class SettingsTab(QWidget):
|
|||||||
self,
|
self,
|
||||||
"Scan Complete",
|
"Scan Complete",
|
||||||
f"Found {total_detected} game(s) with existing installers.\n\n"
|
f"Found {total_detected} game(s) with existing installers.\n\n"
|
||||||
"Their metadata has been recorded. Check the Status tab for details.",
|
"Their metadata has been recorded and they have been marked as managed.\n"
|
||||||
|
"Check the Library and Status tabs for details.",
|
||||||
)
|
)
|
||||||
|
|||||||
+93
-108
@@ -1,6 +1,8 @@
|
|||||||
"""Status tab — installer status overview, update checks, downloads, pruning."""
|
"""Status tab — installer status overview, update checks, downloads, pruning."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt
|
||||||
from PySide6.QtGui import QBrush, QColor
|
from PySide6.QtGui import QBrush, QColor
|
||||||
@@ -22,10 +24,9 @@ from loguru import logger
|
|||||||
|
|
||||||
from src.api import GogApi
|
from src.api import GogApi
|
||||||
from src.config import AppConfig, MetadataStore
|
from src.config import AppConfig, MetadataStore
|
||||||
from src.downloader import InstallerDownloader
|
|
||||||
from src.ui.dialog_game_versions import GameVersionsDialog
|
from src.ui.dialog_game_versions import GameVersionsDialog
|
||||||
|
from src.ui.workers import DownloadWorker, FetchWorker, run_on_thread
|
||||||
from src.models import (
|
from src.models import (
|
||||||
BonusContent,
|
|
||||||
GameStatus,
|
GameStatus,
|
||||||
GameStatusInfo,
|
GameStatusInfo,
|
||||||
InstallerPlatform,
|
InstallerPlatform,
|
||||||
@@ -72,6 +73,10 @@ class StatusTab(QWidget):
|
|||||||
InstallerPlatform.WINDOWS: [],
|
InstallerPlatform.WINDOWS: [],
|
||||||
InstallerPlatform.LINUX: [],
|
InstallerPlatform.LINUX: [],
|
||||||
}
|
}
|
||||||
|
self._active_threads: list = []
|
||||||
|
self._download_worker: DownloadWorker | None = None
|
||||||
|
self._dl_label = ""
|
||||||
|
self._dl_result: tuple[int, int] = (0, 0)
|
||||||
self._setup_ui()
|
self._setup_ui()
|
||||||
|
|
||||||
def _setup_ui(self) -> None:
|
def _setup_ui(self) -> None:
|
||||||
@@ -89,6 +94,11 @@ class StatusTab(QWidget):
|
|||||||
self.download_btn.setEnabled(False)
|
self.download_btn.setEnabled(False)
|
||||||
controls.addWidget(self.download_btn)
|
controls.addWidget(self.download_btn)
|
||||||
|
|
||||||
|
self.cancel_btn = QPushButton("Cancel")
|
||||||
|
self.cancel_btn.clicked.connect(self._cancel_download)
|
||||||
|
self.cancel_btn.setVisible(False)
|
||||||
|
controls.addWidget(self.cancel_btn)
|
||||||
|
|
||||||
self.show_unknown_cb = QCheckBox("Show unversioned/unknown")
|
self.show_unknown_cb = QCheckBox("Show unversioned/unknown")
|
||||||
self.show_unknown_cb.setChecked(False)
|
self.show_unknown_cb.setChecked(False)
|
||||||
self.show_unknown_cb.toggled.connect(self._repopulate_current)
|
self.show_unknown_cb.toggled.connect(self._repopulate_current)
|
||||||
@@ -136,27 +146,47 @@ class StatusTab(QWidget):
|
|||||||
return self.config.windows_path if platform == InstallerPlatform.WINDOWS else self.config.linux_path
|
return self.config.windows_path if platform == InstallerPlatform.WINDOWS else self.config.linux_path
|
||||||
|
|
||||||
def check_updates(self) -> None:
|
def check_updates(self) -> None:
|
||||||
"""Check update status for all managed games, per language."""
|
"""Check update status for all managed games (fetch in a background thread)."""
|
||||||
managed_ids = self.config.managed_game_ids
|
if any(thread.isRunning() for thread, _ in self._active_threads):
|
||||||
|
return # a check is already in progress
|
||||||
|
managed_ids = list(self.config.managed_game_ids)
|
||||||
if not managed_ids:
|
if not managed_ids:
|
||||||
self.status_label.setText("No managed games. Go to Library tab to select games.")
|
self.status_label.setText("No managed games. Go to Library tab to select games.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.api.clear_cache()
|
||||||
self.check_btn.setEnabled(False)
|
self.check_btn.setEnabled(False)
|
||||||
|
self.download_btn.setEnabled(False)
|
||||||
self.status_label.setText("Checking updates...")
|
self.status_label.setText("Checking updates...")
|
||||||
self._update_platform_tab_visibility()
|
self._update_platform_tab_visibility()
|
||||||
|
self.progress_bar.setVisible(True)
|
||||||
|
self.progress_bar.setRange(0, 0)
|
||||||
|
|
||||||
# TODO: Run in a thread to avoid blocking the GUI
|
def fetch(progress: Callable[[int, int], None]) -> object:
|
||||||
|
self._verify_metadata()
|
||||||
|
owned_set = self.api.get_owned_ids_set()
|
||||||
|
products_cache = self.api.get_products(managed_ids, progress=progress)
|
||||||
|
return products_cache, owned_set
|
||||||
|
|
||||||
self._verify_metadata()
|
worker = FetchWorker(fetch)
|
||||||
|
worker.result.connect(self._on_check_fetched)
|
||||||
|
worker.failed.connect(self._on_check_failed)
|
||||||
|
worker.progress.connect(self._on_check_progress)
|
||||||
|
run_on_thread(self, worker)
|
||||||
|
|
||||||
owned_set = self.api.get_owned_ids_set()
|
def _on_check_progress(self, done: int, total: int) -> None:
|
||||||
products_cache: dict[str, dict] = {}
|
self.status_label.setText(f"Fetching game info {done}/{total}...")
|
||||||
for game_id in managed_ids:
|
|
||||||
product = self.api.get_product_info(game_id)
|
|
||||||
if product:
|
|
||||||
products_cache[game_id] = product
|
|
||||||
|
|
||||||
|
def _on_check_failed(self, message: str) -> None:
|
||||||
|
logger.error(f"Update check failed: {message}")
|
||||||
|
self.progress_bar.setVisible(False)
|
||||||
|
self.check_btn.setEnabled(True)
|
||||||
|
self.status_label.setText("Failed to check updates.")
|
||||||
|
|
||||||
|
def _on_check_fetched(self, result: object) -> None:
|
||||||
|
"""Build status and populate trees on the main thread (may show dialogs)."""
|
||||||
|
self.progress_bar.setVisible(False)
|
||||||
|
products_cache, owned_set = cast(tuple[dict[str, dict], set[str]], result)
|
||||||
self._run_check(products_cache, owned_set)
|
self._run_check(products_cache, owned_set)
|
||||||
|
|
||||||
def check_updates_from_cache(self, products_cache: dict[str, dict], owned_set: set[str]) -> None:
|
def check_updates_from_cache(self, products_cache: dict[str, dict], owned_set: set[str]) -> None:
|
||||||
@@ -440,71 +470,14 @@ class StatusTab(QWidget):
|
|||||||
|
|
||||||
return tree_item
|
return tree_item
|
||||||
|
|
||||||
def _run_download_for_platform(self, platform: InstallerPlatform) -> tuple[int, int]:
|
def download_updates(self) -> None:
|
||||||
"""Download all pending installers and bonus content for one platform.
|
"""Download updates for the currently visible platform (in a background thread)."""
|
||||||
|
if self._download_worker is not None:
|
||||||
|
return # a download is already running
|
||||||
|
|
||||||
Returns (items_downloaded, bonus_files_downloaded).
|
platform = self._current_platform()
|
||||||
"""
|
|
||||||
base_path = self._base_path_for(platform)
|
base_path = self._base_path_for(platform)
|
||||||
if not base_path:
|
if not base_path:
|
||||||
return 0, 0
|
|
||||||
|
|
||||||
downloadable = {GameStatus.UPDATE_AVAILABLE, GameStatus.NOT_DOWNLOADED}
|
|
||||||
to_download = [item for item in self.status_items[platform] if item.status in downloadable]
|
|
||||||
for item in self.status_items[platform]:
|
|
||||||
to_download.extend(dlc for dlc in item.dlcs if dlc.status in downloadable)
|
|
||||||
|
|
||||||
# TODO: Run in a thread to avoid blocking the GUI
|
|
||||||
completed = 0
|
|
||||||
if to_download:
|
|
||||||
metadata = MetadataStore(base_path)
|
|
||||||
downloader = InstallerDownloader(self.api, metadata)
|
|
||||||
self.progress_bar.setVisible(True)
|
|
||||||
self.progress_bar.setMaximum(len(to_download))
|
|
||||||
self.progress_bar.setValue(0)
|
|
||||||
|
|
||||||
for item in to_download:
|
|
||||||
self.status_label.setText(f"Downloading: {item.name}...")
|
|
||||||
folder_name = item.parent_name if item.parent_name else item.name
|
|
||||||
effective_langs = self.config.get_effective_languages(item.game_id)
|
|
||||||
installers = self.api.get_installers(item.game_id, platforms=[platform], languages=effective_langs)
|
|
||||||
|
|
||||||
gs = self.config.get_game_settings(item.game_id)
|
|
||||||
is_english_only = gs.english_only if gs.english_only is not None else self.config.english_only
|
|
||||||
single_language = is_english_only or len({i.language for i in installers}) == 1
|
|
||||||
|
|
||||||
for installer in installers:
|
|
||||||
success = downloader.download_installer(installer, folder_name, single_language=single_language)
|
|
||||||
if success:
|
|
||||||
logger.info(f"Downloaded {installer.filename}")
|
|
||||||
else:
|
|
||||||
logger.error(f"Failed to download {installer.filename}")
|
|
||||||
|
|
||||||
completed += 1
|
|
||||||
self.progress_bar.setValue(completed)
|
|
||||||
|
|
||||||
bonus_count = 0
|
|
||||||
if any(item.bonus_available > item.bonus_downloaded for item in self.status_items[platform]):
|
|
||||||
bonus_items_to_download = self._collect_bonus_downloads(platform, base_path)
|
|
||||||
if bonus_items_to_download:
|
|
||||||
self.progress_bar.setVisible(True)
|
|
||||||
self.progress_bar.setMaximum(len(bonus_items_to_download))
|
|
||||||
self.progress_bar.setValue(0)
|
|
||||||
for game_name, bonus in bonus_items_to_download:
|
|
||||||
self.status_label.setText(f"Downloading bonus: {bonus.name}...")
|
|
||||||
b_metadata = MetadataStore(base_path)
|
|
||||||
b_downloader = InstallerDownloader(self.api, b_metadata)
|
|
||||||
if b_downloader.download_bonus(bonus, game_name):
|
|
||||||
bonus_count += 1
|
|
||||||
self.progress_bar.setValue(bonus_count)
|
|
||||||
|
|
||||||
self.progress_bar.setVisible(False)
|
|
||||||
return completed, bonus_count
|
|
||||||
|
|
||||||
def download_updates(self) -> None:
|
|
||||||
"""Download updates for the currently visible platform."""
|
|
||||||
platform = self._current_platform()
|
|
||||||
if not self._base_path_for(platform):
|
|
||||||
self.status_label.setText("No path configured for this platform.")
|
self.status_label.setText("No path configured for this platform.")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -520,46 +493,58 @@ class StatusTab(QWidget):
|
|||||||
|
|
||||||
self.download_btn.setEnabled(False)
|
self.download_btn.setEnabled(False)
|
||||||
self.check_btn.setEnabled(False)
|
self.check_btn.setEnabled(False)
|
||||||
|
self.cancel_btn.setVisible(True)
|
||||||
|
self.cancel_btn.setEnabled(True)
|
||||||
|
self.progress_bar.setVisible(True)
|
||||||
|
self.progress_bar.setRange(0, 0) # busy indicator until first bytes arrive
|
||||||
|
self._dl_result = (0, 0)
|
||||||
|
self._dl_label = ""
|
||||||
|
|
||||||
completed, bonus_count = self._run_download_for_platform(platform)
|
worker = DownloadWorker(
|
||||||
|
self.api, self.config, platform, list(self.status_items[platform]), base_path
|
||||||
|
)
|
||||||
|
self._download_worker = worker
|
||||||
|
worker.byte_progress.connect(self._on_download_bytes)
|
||||||
|
worker.file_progress.connect(self._on_download_file)
|
||||||
|
worker.result.connect(self._on_download_result)
|
||||||
|
worker.done.connect(self._on_download_done)
|
||||||
|
run_on_thread(self, worker)
|
||||||
|
|
||||||
|
def _cancel_download(self) -> None:
|
||||||
|
if self._download_worker is not None:
|
||||||
|
self.cancel_btn.setEnabled(False)
|
||||||
|
self.status_label.setText("Cancelling after current file...")
|
||||||
|
self._download_worker.cancel()
|
||||||
|
|
||||||
|
def _on_download_file(self, index: int, total: int, name: str) -> None:
|
||||||
|
self._dl_label = f"Downloading ({index}/{total}): {name}"
|
||||||
|
self.status_label.setText(self._dl_label)
|
||||||
|
self.progress_bar.setRange(0, 0) # busy until byte progress for this file
|
||||||
|
|
||||||
|
def _on_download_bytes(self, downloaded: int, total: int, speed: float) -> None:
|
||||||
|
if total > 0:
|
||||||
|
self.progress_bar.setRange(0, total)
|
||||||
|
self.progress_bar.setValue(downloaded)
|
||||||
|
speed_mb = speed / (1024 * 1024)
|
||||||
|
self.status_label.setText(f"{self._dl_label} — {speed_mb:.1f} MB/s")
|
||||||
|
|
||||||
|
def _on_download_result(self, completed: int, bonus_count: int) -> None:
|
||||||
|
self._dl_result = (completed, bonus_count)
|
||||||
|
|
||||||
|
def _on_download_done(self) -> None:
|
||||||
|
cancelled = self._download_worker is not None and self._download_worker.is_cancelled()
|
||||||
|
self._download_worker = None
|
||||||
|
self.progress_bar.setVisible(False)
|
||||||
|
self.progress_bar.setRange(0, 100)
|
||||||
|
self.cancel_btn.setVisible(False)
|
||||||
self.check_btn.setEnabled(True)
|
self.check_btn.setEnabled(True)
|
||||||
|
|
||||||
|
completed, bonus_count = self._dl_result
|
||||||
parts = [f"Downloaded {completed} items"]
|
parts = [f"Downloaded {completed} items"]
|
||||||
if bonus_count:
|
if bonus_count:
|
||||||
parts.append(f"{bonus_count} bonus files")
|
parts.append(f"{bonus_count} bonus files")
|
||||||
self.status_label.setText(f"{', '.join(parts)}. Run 'Check for Updates' to refresh.")
|
prefix = "Cancelled. " if cancelled else ""
|
||||||
|
self.status_label.setText(f"{prefix}{', '.join(parts)}. Run 'Check for Updates' to refresh.")
|
||||||
def _collect_bonus_downloads(
|
|
||||||
self, platform: InstallerPlatform, base_path: str,
|
|
||||||
) -> list[tuple[str, BonusContent]]:
|
|
||||||
"""Collect bonus content not yet downloaded for this platform."""
|
|
||||||
result: list[tuple[str, BonusContent]] = []
|
|
||||||
metadata = MetadataStore(base_path)
|
|
||||||
platform_key = "windows" if platform == InstallerPlatform.WINDOWS else "linux"
|
|
||||||
|
|
||||||
for item in self.status_items[platform]:
|
|
||||||
if item.bonus_available <= item.bonus_downloaded:
|
|
||||||
continue
|
|
||||||
|
|
||||||
record = metadata.get_game(item.game_id)
|
|
||||||
if not record or not record.installers:
|
|
||||||
product = self.api.get_product_info(item.game_id)
|
|
||||||
if not product:
|
|
||||||
continue
|
|
||||||
has_platform = any(
|
|
||||||
inst.get("os") == platform_key
|
|
||||||
for inst in product.get("downloads", {}).get("installers", [])
|
|
||||||
)
|
|
||||||
if not has_platform:
|
|
||||||
continue
|
|
||||||
|
|
||||||
downloaded_names = {b.name for b in record.bonuses} if record else set()
|
|
||||||
folder_name = item.parent_name if item.parent_name else item.name
|
|
||||||
for bonus in self.api.get_bonus_content(item.game_id):
|
|
||||||
if bonus.name not in downloaded_names:
|
|
||||||
result.append((folder_name, bonus))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _on_item_double_clicked(self, item: QTreeWidgetItem, column: int) -> None:
|
def _on_item_double_clicked(self, item: QTreeWidgetItem, column: int) -> None:
|
||||||
"""Open version management dialog for the clicked game."""
|
"""Open version management dialog for the clicked game."""
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
"""Background workers for network/IO-heavy operations.
|
||||||
|
|
||||||
|
Qt widgets must only be touched from the main thread, so these workers do the
|
||||||
|
slow work (API calls, downloads) on a separate QThread and communicate results
|
||||||
|
back via signals.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
from PySide6.QtCore import QObject, QThread, Signal
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from src.api import GogApi
|
||||||
|
from src.config import AppConfig, MetadataStore
|
||||||
|
from src.downloader import InstallerDownloader
|
||||||
|
from src.models import BonusContent, GameStatus, GameStatusInfo, InstallerPlatform
|
||||||
|
|
||||||
|
|
||||||
|
class _BaseWorker(QObject):
|
||||||
|
"""Base worker. Subclasses implement run() and emit `done` when finished."""
|
||||||
|
|
||||||
|
done = Signal() # always emitted when run() returns (success or failure)
|
||||||
|
|
||||||
|
def run(self) -> None: # pragma: no cover - overridden
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
def run_on_thread(owner: QObject, worker: _BaseWorker) -> QThread:
|
||||||
|
"""Move `worker` to a fresh QThread and start it.
|
||||||
|
|
||||||
|
Connect the worker's own signals BEFORE calling this. References to BOTH the
|
||||||
|
thread and the worker are kept on `owner._active_threads` so neither is
|
||||||
|
garbage-collected mid-run (a GC'd worker would never have its `run` slot
|
||||||
|
invoked), and they are cleaned up when the thread finishes.
|
||||||
|
"""
|
||||||
|
logger.debug(f"run_on_thread: starting {type(worker).__name__}")
|
||||||
|
thread = QThread()
|
||||||
|
worker.moveToThread(thread)
|
||||||
|
thread.started.connect(worker.run)
|
||||||
|
worker.done.connect(thread.quit)
|
||||||
|
worker.done.connect(worker.deleteLater)
|
||||||
|
thread.finished.connect(thread.deleteLater)
|
||||||
|
thread.finished.connect(lambda: logger.debug("run_on_thread: thread finished"))
|
||||||
|
|
||||||
|
if not hasattr(owner, "_active_threads"):
|
||||||
|
owner._active_threads = [] # type: ignore[attr-defined]
|
||||||
|
threads: list[tuple[QThread, _BaseWorker]] = owner._active_threads # type: ignore[attr-defined]
|
||||||
|
entry = (thread, worker)
|
||||||
|
threads.append(entry)
|
||||||
|
thread.finished.connect(lambda: threads.remove(entry) if entry in threads else None)
|
||||||
|
thread.start()
|
||||||
|
return thread
|
||||||
|
|
||||||
|
|
||||||
|
class FetchWorker(_BaseWorker):
|
||||||
|
"""Runs an arbitrary network-fetch callable off the main thread.
|
||||||
|
|
||||||
|
The callable receives a progress callback `progress(done, total)` and returns
|
||||||
|
any result object, delivered via the `result` signal. UI work (building trees,
|
||||||
|
showing dialogs) must be done by the slot connected to `result`, on the main
|
||||||
|
thread.
|
||||||
|
"""
|
||||||
|
|
||||||
|
progress = Signal(int, int) # done, total
|
||||||
|
result = Signal(object)
|
||||||
|
failed = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, fn: Callable[[Callable[[int, int], None]], object]) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self._fn = fn
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
logger.debug("FetchWorker.run: begin")
|
||||||
|
try:
|
||||||
|
res = self._fn(self._emit_progress)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Fetch worker error: {e}")
|
||||||
|
self.failed.emit(str(e))
|
||||||
|
self.done.emit()
|
||||||
|
return
|
||||||
|
logger.debug("FetchWorker.run: emitting result")
|
||||||
|
self.result.emit(res)
|
||||||
|
self.done.emit()
|
||||||
|
|
||||||
|
def _emit_progress(self, done: int, total: int) -> None:
|
||||||
|
self.progress.emit(done, total)
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadWorker(_BaseWorker):
|
||||||
|
"""Downloads all pending installers and bonus content for one platform.
|
||||||
|
|
||||||
|
Implements the DownloadProgressCallback protocol so it can be passed straight
|
||||||
|
to InstallerDownloader for per-byte progress reporting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
byte_progress = Signal(int, int, float) # downloaded, total, speed (current file)
|
||||||
|
file_progress = Signal(int, int, str) # current index (1-based), total, name
|
||||||
|
result = Signal(int, int) # installers_done, bonus_files_done
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
api: GogApi,
|
||||||
|
config: AppConfig,
|
||||||
|
platform: InstallerPlatform,
|
||||||
|
items: list[GameStatusInfo],
|
||||||
|
base_path: str,
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.api = api
|
||||||
|
self.config = config
|
||||||
|
self.platform = platform
|
||||||
|
self.items = items
|
||||||
|
self.base_path = base_path
|
||||||
|
self._cancelled = False
|
||||||
|
|
||||||
|
def cancel(self) -> None:
|
||||||
|
"""Request cancellation (safe to call from the main thread)."""
|
||||||
|
self._cancelled = True
|
||||||
|
|
||||||
|
# --- DownloadProgressCallback protocol ---
|
||||||
|
def on_progress(self, downloaded: int, total: int, speed: float) -> None:
|
||||||
|
self.byte_progress.emit(downloaded, total, speed)
|
||||||
|
|
||||||
|
def on_finished(self, success: bool, message: str) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_cancelled(self) -> bool:
|
||||||
|
return self._cancelled
|
||||||
|
|
||||||
|
# --- worker body ---
|
||||||
|
def run(self) -> None:
|
||||||
|
completed = 0
|
||||||
|
bonus_count = 0
|
||||||
|
try:
|
||||||
|
completed, bonus_count = self._run()
|
||||||
|
except Exception as e: # defensive: never let a worker thread crash silently
|
||||||
|
logger.error(f"Download worker error: {e}")
|
||||||
|
finally:
|
||||||
|
self.result.emit(completed, bonus_count)
|
||||||
|
self.done.emit()
|
||||||
|
|
||||||
|
def _run(self) -> tuple[int, int]:
|
||||||
|
if not self.base_path:
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
|
downloadable = {GameStatus.UPDATE_AVAILABLE, GameStatus.NOT_DOWNLOADED}
|
||||||
|
to_download = [item for item in self.items if item.status in downloadable]
|
||||||
|
for item in self.items:
|
||||||
|
to_download.extend(dlc for dlc in item.dlcs if dlc.status in downloadable)
|
||||||
|
|
||||||
|
metadata = MetadataStore(self.base_path)
|
||||||
|
downloader = InstallerDownloader(self.api, metadata)
|
||||||
|
bonus_list = self._collect_bonus(metadata)
|
||||||
|
|
||||||
|
total = len(to_download) + len(bonus_list)
|
||||||
|
index = 0
|
||||||
|
completed = 0
|
||||||
|
|
||||||
|
for item in to_download:
|
||||||
|
if self._cancelled:
|
||||||
|
break
|
||||||
|
index += 1
|
||||||
|
self.file_progress.emit(index, total, item.name)
|
||||||
|
folder_name = item.parent_name if item.parent_name else item.name
|
||||||
|
effective_langs = self.config.get_effective_languages(item.game_id)
|
||||||
|
installers = self.api.get_installers(
|
||||||
|
item.game_id, platforms=[self.platform], languages=effective_langs
|
||||||
|
)
|
||||||
|
|
||||||
|
gs = self.config.get_game_settings(item.game_id)
|
||||||
|
is_english_only = gs.english_only if gs.english_only is not None else self.config.english_only
|
||||||
|
single_language = is_english_only or len({i.language for i in installers}) == 1
|
||||||
|
|
||||||
|
for installer in installers:
|
||||||
|
if self._cancelled:
|
||||||
|
break
|
||||||
|
ok = downloader.download_installer(
|
||||||
|
installer, folder_name, single_language=single_language, callback=self
|
||||||
|
)
|
||||||
|
if not ok and not self._cancelled:
|
||||||
|
logger.error(f"Failed to download {installer.filename}")
|
||||||
|
completed += 1
|
||||||
|
|
||||||
|
bonus_count = 0
|
||||||
|
for game_name, bonus in bonus_list:
|
||||||
|
if self._cancelled:
|
||||||
|
break
|
||||||
|
index += 1
|
||||||
|
self.file_progress.emit(index, total, bonus.name)
|
||||||
|
if downloader.download_bonus(bonus, game_name, callback=self):
|
||||||
|
bonus_count += 1
|
||||||
|
|
||||||
|
return completed, bonus_count
|
||||||
|
|
||||||
|
def _collect_bonus(self, metadata: MetadataStore) -> list[tuple[str, BonusContent]]:
|
||||||
|
"""Collect bonus content not yet downloaded for this platform."""
|
||||||
|
result: list[tuple[str, BonusContent]] = []
|
||||||
|
platform_key = "windows" if self.platform == InstallerPlatform.WINDOWS else "linux"
|
||||||
|
|
||||||
|
for item in self.items:
|
||||||
|
if item.bonus_available <= item.bonus_downloaded:
|
||||||
|
continue
|
||||||
|
|
||||||
|
record = metadata.get_game(item.game_id)
|
||||||
|
if not record or not record.installers:
|
||||||
|
product = self.api.get_product_info(item.game_id)
|
||||||
|
if not product:
|
||||||
|
continue
|
||||||
|
has_platform = any(
|
||||||
|
inst.get("os") == platform_key
|
||||||
|
for inst in product.get("downloads", {}).get("installers", [])
|
||||||
|
)
|
||||||
|
if not has_platform:
|
||||||
|
continue
|
||||||
|
|
||||||
|
downloaded_names = {b.name for b in record.bonuses} if record else set()
|
||||||
|
folder_name = item.parent_name if item.parent_name else item.name
|
||||||
|
for bonus in self.api.get_bonus_content(item.game_id):
|
||||||
|
if bonus.name not in downloaded_names:
|
||||||
|
result.append((folder_name, bonus))
|
||||||
|
|
||||||
|
return result
|
||||||
+23
-116
@@ -1,141 +1,48 @@
|
|||||||
"""Tests for constants module."""
|
"""Tests for the constants module."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import mock_open, patch
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from src.constants import (
|
from src.constants import (
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
APP_TITLE,
|
APP_TITLE,
|
||||||
APP_VERSION,
|
VERSION,
|
||||||
ENV_DEBUG,
|
_load_debug,
|
||||||
get_debug_mode,
|
_load_version,
|
||||||
get_version,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
def test_load_version_returns_string() -> None:
|
||||||
# get_version()
|
assert isinstance(_load_version(), str)
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_version_returns_string() -> None:
|
def test_load_version_semver_format() -> None:
|
||||||
"""get_version() should return a string."""
|
assert re.match(r"^\d+\.\d+\.\d+", _load_version()), f"Not semver: {_load_version()!r}"
|
||||||
assert isinstance(get_version(), str)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_version_semver_format() -> None:
|
def test_version_has_v_prefix() -> None:
|
||||||
"""get_version() should return a semver-like string X.Y.Z."""
|
assert VERSION.startswith("v")
|
||||||
version = get_version()
|
|
||||||
assert re.match(r"^\d+\.\d+\.\d+", version), f"Not semver: {version!r}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_version_fallback_when_toml_missing(tmp_path: Path) -> None:
|
|
||||||
"""get_version() returns '0.0.0-unknown' when pyproject.toml and _version.py are both missing."""
|
|
||||||
missing = tmp_path / "nonexistent.toml"
|
|
||||||
with patch("src.constants._PYPROJECT_PATH", missing):
|
|
||||||
result = get_version()
|
|
||||||
# Either fallback _version.py exists (from previous run) or returns unknown
|
|
||||||
assert isinstance(result, str)
|
|
||||||
assert len(result) > 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_version_unknown_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
"""get_version() returns '0.0.0-unknown' when all sources are unavailable."""
|
|
||||||
missing = tmp_path / "nonexistent.toml"
|
|
||||||
monkeypatch.setattr("src.constants._PYPROJECT_PATH", missing)
|
|
||||||
|
|
||||||
# Patch _version import to also fail
|
|
||||||
with patch("src.constants.Path.write_text", side_effect=OSError):
|
|
||||||
with patch.dict("sys.modules", {"src._version": None}):
|
|
||||||
result = get_version()
|
|
||||||
|
|
||||||
assert isinstance(result, str)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# get_debug_mode()
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_returns_bool() -> None:
|
|
||||||
"""get_debug_mode() should always return a bool."""
|
|
||||||
assert isinstance(get_debug_mode(), bool)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_true(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
"""get_debug_mode() returns True when ENV_DEBUG=true."""
|
|
||||||
monkeypatch.setenv("ENV_DEBUG", "true")
|
|
||||||
assert get_debug_mode() is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_true_variants(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
"""get_debug_mode() accepts '1' and 'yes' as truthy values."""
|
|
||||||
for value in ("1", "yes", "YES", "True", "TRUE"):
|
|
||||||
monkeypatch.setenv("ENV_DEBUG", value)
|
|
||||||
assert get_debug_mode() is True, f"Expected True for ENV_DEBUG={value!r}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_false(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
"""get_debug_mode() returns False when ENV_DEBUG=false."""
|
|
||||||
monkeypatch.setenv("ENV_DEBUG", "false")
|
|
||||||
assert get_debug_mode() is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_false_when_unset(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
"""get_debug_mode() returns False when ENV_DEBUG is not set."""
|
|
||||||
monkeypatch.delenv("ENV_DEBUG", raising=False)
|
|
||||||
assert get_debug_mode() is False
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Module-level constants
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def test_env_debug_is_bool() -> None:
|
|
||||||
"""ENV_DEBUG should be a bool."""
|
|
||||||
assert isinstance(ENV_DEBUG, bool)
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_version_is_string() -> None:
|
|
||||||
"""APP_VERSION should be a string."""
|
|
||||||
assert isinstance(APP_VERSION, str)
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_version_semver_format() -> None:
|
|
||||||
"""APP_VERSION should follow semver format X.Y.Z."""
|
|
||||||
assert re.match(r"^\d+\.\d+\.\d+", APP_VERSION), f"Not semver: {APP_VERSION!r}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_name_value() -> None:
|
def test_app_name_value() -> None:
|
||||||
"""APP_NAME should be 'X4 SavEd'."""
|
assert APP_NAME == "GOGUpdater"
|
||||||
assert APP_NAME == "X4 SavEd"
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_title_contains_name_and_version() -> None:
|
def test_app_title_contains_version() -> None:
|
||||||
"""APP_TITLE should contain APP_NAME and APP_VERSION."""
|
assert VERSION in APP_TITLE
|
||||||
assert APP_NAME in APP_TITLE
|
|
||||||
assert APP_VERSION in APP_TITLE
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_title_dev_suffix_when_debug(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_load_debug_returns_bool() -> None:
|
||||||
"""APP_TITLE ends with '-DEV' when ENV_DEBUG is True."""
|
assert isinstance(_load_debug(), bool)
|
||||||
import importlib
|
|
||||||
import src.constants as consts
|
|
||||||
|
|
||||||
monkeypatch.setenv("ENV_DEBUG", "true")
|
|
||||||
monkeypatch.setattr(consts, "ENV_DEBUG", True)
|
|
||||||
title = f"{consts.APP_NAME} v{consts.APP_VERSION}" + ("-DEV" if True else "")
|
|
||||||
assert title.endswith("-DEV")
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_title_no_dev_suffix_when_not_debug(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_load_debug_true_variants(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""APP_TITLE does not end with '-DEV' when ENV_DEBUG is False."""
|
for value in ("true", "1", "yes", "YES", "True"):
|
||||||
import src.constants as consts
|
monkeypatch.setenv("ENV_DEBUG", value)
|
||||||
|
assert _load_debug() is True, f"Expected True for ENV_DEBUG={value!r}"
|
||||||
|
|
||||||
monkeypatch.setattr(consts, "ENV_DEBUG", False)
|
|
||||||
title = f"{consts.APP_NAME} v{consts.APP_VERSION}" + ("-DEV" if False else "")
|
def test_load_debug_false_when_unset(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
assert not title.endswith("-DEV")
|
monkeypatch.delenv("ENV_DEBUG", raising=False)
|
||||||
|
assert _load_debug() is False
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"""Tests for atomic file writes."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from src.fileutil import atomic_write_text
|
||||||
|
|
||||||
|
|
||||||
|
def test_writes_content(tmp_path: Path) -> None:
|
||||||
|
target = tmp_path / "out.json"
|
||||||
|
atomic_write_text(target, '{"a": 1}')
|
||||||
|
assert target.read_text(encoding="utf-8") == '{"a": 1}'
|
||||||
|
|
||||||
|
|
||||||
|
def test_creates_parent_dirs(tmp_path: Path) -> None:
|
||||||
|
target = tmp_path / "nested" / "deep" / "out.txt"
|
||||||
|
atomic_write_text(target, "hi")
|
||||||
|
assert target.read_text(encoding="utf-8") == "hi"
|
||||||
|
|
||||||
|
|
||||||
|
def test_overwrites_existing(tmp_path: Path) -> None:
|
||||||
|
target = tmp_path / "out.txt"
|
||||||
|
target.write_text("old", encoding="utf-8")
|
||||||
|
atomic_write_text(target, "new")
|
||||||
|
assert target.read_text(encoding="utf-8") == "new"
|
||||||
|
|
||||||
|
|
||||||
|
def test_leaves_no_temp_files_on_success(tmp_path: Path) -> None:
|
||||||
|
target = tmp_path / "out.txt"
|
||||||
|
atomic_write_text(target, "data")
|
||||||
|
assert [p.name for p in tmp_path.iterdir()] == ["out.txt"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_original_intact_when_write_fails(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
target = tmp_path / "out.txt"
|
||||||
|
target.write_text("original", encoding="utf-8")
|
||||||
|
|
||||||
|
def boom(*args: object, **kwargs: object) -> None:
|
||||||
|
raise OSError("disk full")
|
||||||
|
|
||||||
|
monkeypatch.setattr("os.replace", boom)
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
atomic_write_text(target, "new")
|
||||||
|
|
||||||
|
# Original file is untouched and no temp file is left behind.
|
||||||
|
assert target.read_text(encoding="utf-8") == "original"
|
||||||
|
assert sorted(p.name for p in tmp_path.iterdir()) == ["out.txt"]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"""Tests for pure helpers in src.models."""
|
||||||
|
|
||||||
|
from src.models import language_folder_name, sanitize_folder_name
|
||||||
|
|
||||||
|
|
||||||
|
class TestSanitizeFolderName:
|
||||||
|
def test_plain_name_unchanged(self) -> None:
|
||||||
|
assert sanitize_folder_name("Fallout 2") == "Fallout 2"
|
||||||
|
|
||||||
|
def test_colon_becomes_dash(self) -> None:
|
||||||
|
assert sanitize_folder_name("Baldur's Gate: Dark Alliance") == "Baldur's Gate - Dark Alliance"
|
||||||
|
|
||||||
|
def test_strips_trademark_symbols(self) -> None:
|
||||||
|
assert sanitize_folder_name("Game™ Title®") == "Game Title"
|
||||||
|
|
||||||
|
def test_removes_invalid_filesystem_chars(self) -> None:
|
||||||
|
# ':' becomes ' - ' first, then <>"/\|?* are stripped.
|
||||||
|
assert sanitize_folder_name('A<B>C:D"E/F\\G|H?I*J') == "ABC - DEFGHIJ"
|
||||||
|
|
||||||
|
def test_collapses_multiple_spaces(self) -> None:
|
||||||
|
assert sanitize_folder_name("A B") == "A B"
|
||||||
|
|
||||||
|
def test_collapses_adjacent_dashes_from_colons(self) -> None:
|
||||||
|
assert sanitize_folder_name("A: : B") == "A - B"
|
||||||
|
|
||||||
|
def test_strips_leading_trailing_dots_and_space(self) -> None:
|
||||||
|
assert sanitize_folder_name(" .Title. ") == "Title"
|
||||||
|
|
||||||
|
def test_idempotent(self) -> None:
|
||||||
|
once = sanitize_folder_name("Witcher 3: Wild Hunt™")
|
||||||
|
assert sanitize_folder_name(once) == once
|
||||||
|
|
||||||
|
|
||||||
|
class TestLanguageFolderName:
|
||||||
|
def test_known_code(self) -> None:
|
||||||
|
assert language_folder_name("cs") == "Czech"
|
||||||
|
|
||||||
|
def test_unknown_code_passthrough(self) -> None:
|
||||||
|
assert language_folder_name("xx") == "xx"
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""Tests for prune version-selection logic (MetadataStore._select_versions_to_keep)."""
|
||||||
|
|
||||||
|
from src.config import MetadataStore
|
||||||
|
from src.models import DownloadedInstaller, GameRecord, InstallerType, PruneStrategy
|
||||||
|
|
||||||
|
|
||||||
|
def _record(version_years: dict[str, int]) -> GameRecord:
|
||||||
|
"""Build a GameRecord with one installer per (version, year)."""
|
||||||
|
installers = [
|
||||||
|
DownloadedInstaller(
|
||||||
|
filename=f"setup_{v}.exe",
|
||||||
|
size=1,
|
||||||
|
version=v,
|
||||||
|
language="en",
|
||||||
|
installer_type=InstallerType.GAME,
|
||||||
|
downloaded_at=f"{year}-01-01T00:00:00",
|
||||||
|
)
|
||||||
|
for v, year in version_years.items()
|
||||||
|
]
|
||||||
|
return GameRecord(game_id="1", name="Game", installers=installers)
|
||||||
|
|
||||||
|
|
||||||
|
VERSION_YEARS = {"1": 2020, "2": 2020, "3": 2021, "4": 2022, "5": 2022}
|
||||||
|
ALL = ["1", "2", "3", "4", "5"]
|
||||||
|
|
||||||
|
|
||||||
|
def _keep(strategy: PruneStrategy, keep_latest: int = 1) -> set[str]:
|
||||||
|
record = _record(VERSION_YEARS)
|
||||||
|
return MetadataStore._select_versions_to_keep(record, ALL, keep_latest, strategy)
|
||||||
|
|
||||||
|
|
||||||
|
def test_returns_all_when_fewer_than_keep() -> None:
|
||||||
|
record = _record({"1": 2020})
|
||||||
|
assert MetadataStore._select_versions_to_keep(record, ["1"], 1, PruneStrategy.LATEST_N) == {"1"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_latest_n_keeps_only_recent() -> None:
|
||||||
|
assert _keep(PruneStrategy.LATEST_N, keep_latest=1) == {"5"}
|
||||||
|
assert _keep(PruneStrategy.LATEST_N, keep_latest=2) == {"4", "5"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_latest_n_oldest_adds_first() -> None:
|
||||||
|
assert _keep(PruneStrategy.LATEST_N_OLDEST, keep_latest=1) == {"1", "5"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_yearly_keeps_one_per_year_plus_latest() -> None:
|
||||||
|
assert _keep(PruneStrategy.YEARLY, keep_latest=1) == {"2", "3", "5"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_latest_n_yearly_oldest_combines_all() -> None:
|
||||||
|
assert _keep(PruneStrategy.LATEST_N_YEARLY_OLDEST, keep_latest=1) == {"1", "2", "3", "5"}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""Tests for version comparison logic."""
|
||||||
|
|
||||||
|
from src.version_compare import CompareResult, compare_versions, normalize_version
|
||||||
|
|
||||||
|
|
||||||
|
class TestNormalizeVersion:
|
||||||
|
def test_strips_gog_suffix(self) -> None:
|
||||||
|
assert normalize_version("2.2 (gog-3)") == "2.2"
|
||||||
|
|
||||||
|
def test_strips_galaxy_suffix(self) -> None:
|
||||||
|
assert normalize_version("1.0 (Galaxy)") == "1.0"
|
||||||
|
|
||||||
|
def test_plain_unchanged(self) -> None:
|
||||||
|
assert normalize_version("1.63") == "1.63"
|
||||||
|
|
||||||
|
|
||||||
|
class TestCompareVersions:
|
||||||
|
def test_equal_exact(self) -> None:
|
||||||
|
assert compare_versions("1.63", "1.63") == CompareResult.EQUAL
|
||||||
|
|
||||||
|
def test_equal_after_normalize(self) -> None:
|
||||||
|
assert compare_versions("2.2", "2.2 (gog-3)") == CompareResult.EQUAL
|
||||||
|
|
||||||
|
def test_older(self) -> None:
|
||||||
|
assert compare_versions("1.52", "1.63") == CompareResult.OLDER
|
||||||
|
|
||||||
|
def test_newer(self) -> None:
|
||||||
|
assert compare_versions("2.0", "1.9") == CompareResult.NEWER
|
||||||
|
|
||||||
|
def test_different_part_counts_padded(self) -> None:
|
||||||
|
assert compare_versions("1.0", "1.0.1") == CompareResult.OLDER
|
||||||
|
assert compare_versions("1.0.0", "1.0") == CompareResult.EQUAL
|
||||||
|
|
||||||
|
def test_empty_is_ambiguous(self) -> None:
|
||||||
|
assert compare_versions("", "1.0") == CompareResult.AMBIGUOUS
|
||||||
|
assert compare_versions("1.0", "") == CompareResult.AMBIGUOUS
|
||||||
|
|
||||||
|
def test_non_numeric_is_ambiguous(self) -> None:
|
||||||
|
assert compare_versions("alpha", "beta") == CompareResult.AMBIGUOUS
|
||||||
|
|
||||||
|
def test_numeric_vs_nonnumeric_is_ambiguous(self) -> None:
|
||||||
|
# "2.2(gog-3)" normalizes to "2.2" (numeric), "2.3a" is not numeric
|
||||||
|
assert compare_versions("2.2 (gog-3)", "2.3a") == CompareResult.AMBIGUOUS
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
"""Tests for background worker threading helpers."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||||
|
|
||||||
|
from PySide6.QtCore import QObject # noqa: E402
|
||||||
|
from PySide6.QtWidgets import QApplication # noqa: E402
|
||||||
|
|
||||||
|
from src.ui.workers import FetchWorker, run_on_thread # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _app() -> QApplication:
|
||||||
|
return QApplication.instance() or QApplication([])
|
||||||
|
|
||||||
|
|
||||||
|
def _spin_until(app: QApplication, predicate, timeout: float = 5.0) -> None:
|
||||||
|
deadline = time.time() + timeout
|
||||||
|
while not predicate() and time.time() < deadline:
|
||||||
|
app.processEvents()
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
def test_worker_runs_when_only_local_reference() -> None:
|
||||||
|
"""Regression: a worker created in a local scope must still run.
|
||||||
|
|
||||||
|
run_on_thread must keep the worker alive; otherwise it is garbage-collected
|
||||||
|
and its `run` slot is never invoked (the 'refresh does nothing' bug).
|
||||||
|
"""
|
||||||
|
app = _app()
|
||||||
|
owner = QObject()
|
||||||
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
|
def start() -> None:
|
||||||
|
worker = FetchWorker(lambda progress: 42)
|
||||||
|
worker.result.connect(lambda r: captured.update(result=r))
|
||||||
|
run_on_thread(owner, worker)
|
||||||
|
# `worker` goes out of scope here
|
||||||
|
|
||||||
|
start()
|
||||||
|
_spin_until(app, lambda: "result" in captured)
|
||||||
|
assert captured.get("result") == 42
|
||||||
|
|
||||||
|
|
||||||
|
def test_progress_and_cleanup() -> None:
|
||||||
|
app = _app()
|
||||||
|
owner = QObject()
|
||||||
|
progress: list[tuple[int, int]] = []
|
||||||
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
|
def fn(report) -> str:
|
||||||
|
report(1, 2)
|
||||||
|
report(2, 2)
|
||||||
|
return "ok"
|
||||||
|
|
||||||
|
worker = FetchWorker(fn)
|
||||||
|
worker.progress.connect(lambda d, t: progress.append((d, t)))
|
||||||
|
worker.result.connect(lambda r: captured.update(result=r))
|
||||||
|
run_on_thread(owner, worker)
|
||||||
|
|
||||||
|
_spin_until(app, lambda: "result" in captured)
|
||||||
|
# let the thread.finished cleanup run
|
||||||
|
_spin_until(app, lambda: not owner._active_threads)
|
||||||
|
|
||||||
|
assert captured.get("result") == "ok"
|
||||||
|
assert (2, 2) in progress
|
||||||
|
assert owner._active_threads == []
|
||||||
Reference in New Issue
Block a user