Initial commit

This commit is contained in:
Marc Koch 2025-09-07 19:39:23 +02:00
commit b92027a780
22 changed files with 1985 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.13

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright © 2025 Marc Koch
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

18
README.md Normal file
View File

@ -0,0 +1,18 @@
# gnome-extension-download-url
A simple script to get the download URL of a GNOME Shell extension from its UUID.
## Installation
You can download the via pipx:
```bash
pipx install --index-url https://git.extrasolar.space/api/packages/marc/pypi/simple/ --pip-args="--extra-index-url https://pypi.org/simple" gnome-extension-download-ur
```
## Usage
```bash
gnome-extension-download-url [EXTENSION EXTENSION_VERSION] [GNOME_SHELL_VERSION] [-h|--help]
```
- `EXTENSION`: the extension UUID (e.g. gsconnect@andyholmes.github.io)
- `EXTENSION_VERSION`: the version of the extension (optional, default: latest, e.g. 66 or latest)
- `GNOME_SHELL_VERSION`: the version of GNOME shell (optional, default: latest, e.g. 46, 3.36 or latest)
- `-h`, `--help`: show help message

44
pyproject.toml Normal file
View File

@ -0,0 +1,44 @@
[build-system]
requires = ["uv_build>=0.8.15,<0.9.0"]
build-backend = "uv_build"
[project]
name = "gnome-extension-download-url"
authors = [
{name = "Marc Koch", email = "marc-koch@posteo.de"},
]
version = "1.0.0"
description = "Get the download URL for a GNOME shell extension from extensions.gnome.org"
readme = "README.md"
license = "MIT"
keywords = ["gnome"]
license-files = ["LICENSE"]
requires-python = ">=3.13"
dependencies = [
"requests>=2.32.5",
]
[dependency-groups]
dev = [
"pytest>=8.4.2",
"pytest-mock>=3.15.0",
]
[project.urls]
Homepage = "https://git.extrasolar.space/marc/gnome-extension-download-url"
[project.scripts]
gnome-extension-download-url = "gnome_extension_download_url.__main__:main"
[tool.uv]
package = true
[tool.uv.build-backend]
module-root = "src"
module-name = "gnome_extension_download_url"
source-exclude = ["tests"]
[[tool.uv.index]]
name = "extrasolar"
url = "https://git.extrasolar.space/api/packages/marc/pypi"
publish-url = "https://git.extrasolar.space/api/packages/marc/pypi"

View File

@ -0,0 +1,153 @@
import sys
import requests
HELP_MSG = \
"""Get the download URL for a GNOME extension.
Usage: gnome-extension-download-url [EXTENSION EXTENSION_VERSION] [GNOME_SHELL_VERSION] [-h|--help]
EXTENSION: the extension UUID (e.g. gsconnect@andyholmes.github.io)
EXTENSION_VERSION: the version of the extension (optional, default: latest, e.g. 66 or latest)
GNOME_SHELL_VERSION: the version of GNOME shell (optional, default: latest, e.g. 46, 3.36 or latest)
-h, --help: show this help message
"""
def print_help(error: bool = False):
"""
Print help message
:param error: if True, print to stderr
"""
print(HELP_MSG, file=sys.stderr if error else sys.stdout)
def check_gnome_shell_version(target_ver: str, shell_versions: list):
"""
Check if the GNOME shell version is available in the list of supported versions
:param target_ver: gnome shell version to check
:param shell_versions: list of supported gnome shell versions
:return: True if valid, False otherwise
"""
# Split the version into its parts
parts = [int(p) for p in target_ver.split(".")]
major, minor, point = (parts + [-1, -1, -1])[:3]
# Check if the version is in the list of supported versions
for ver in shell_versions:
if (ver["major"], ver["minor"], ver["point"]) == (major, minor, point):
return True
return False
def get_gnome_extension_download_url(ext_uuid: str, ext_ver: str = None,
gnome_shell_ver: str = None):
"""
Get the download url from a GNOME extension
:param ext_uuid: the extension UUID (e.g. gsconnect@andyholmes.github.io)
:param ext_ver: extension version
:param gnome_shell_ver: gnome shell version
"""
# Convert "latest" versions to None
ext_ver = None if ext_ver == "latest" else ext_ver
gnome_shell_ver = None if gnome_shell_ver == "latest" else gnome_shell_ver
# Parameter validation
if ext_uuid == "":
raise Exception("Extension UUID cannot be empty")
if ext_ver is not None:
if not str(ext_ver).isdigit():
raise Exception(f"Invalid GNOME extension version: {ext_ver}")
if gnome_shell_ver is not None:
version_parts = str(gnome_shell_ver).split(".")
if len(version_parts) > 3 or not all(
v.isdigit() and int(v) >= 0 for v in version_parts):
raise Exception(f"Invalid GNOME shell version: {gnome_shell_ver}")
# Prepare URL
url = f"https://extensions.gnome.org/api/v1/extensions/{ext_uuid}/versions"
# If an extension version is specified, try to get it directly
if ext_ver:
url = url + f"/{ext_ver}"
result = requests.get(url)
data = result.json()
if result.status_code == 404 or data.get("count") == 0:
if ext_ver is not None:
raise Exception(
f"Extension '{ext_uuid}' version {ext_ver} not found")
raise Exception(f"Extension '{ext_uuid}' not found")
elif result.status_code != 200:
raise Exception(
f"Error fetching extension '{ext_uuid}': {result.status_code}")
if gnome_shell_ver is not None and not check_gnome_shell_version(
gnome_shell_ver, data["shell_versions"]):
raise Exception(f"Extension version {ext_ver} is not compatible "
f"with GNOME shell version {gnome_shell_ver}")
return url + "/?format=zip"
# If no extension version is specified, get the list of versions
versions = []
next_page = True
next_page_url = url
while next_page:
result = requests.get(next_page_url)
data = result.json()
if result.status_code == 404 or data.get("count") == 0:
raise Exception(f"Extension '{ext_uuid}' not found")
elif result.status_code != 200:
raise Exception(
f"Error fetching extension '{ext_uuid}': {result.status_code}")
next_page_url = data["next"]
next_page = data["next"] is not None
versions.extend(data["results"])
versions_sorted = sorted(versions, key=lambda ver: ver["version"],
reverse=True)
# If a GNOME shell version is specified, find the latest compatible version
if gnome_shell_ver:
for version in versions_sorted:
if check_gnome_shell_version(gnome_shell_ver,
version["shell_versions"]):
return url + f"/{version['version']}/?format=zip"
raise Exception("No compatible version found for GNOME shell "
f"version {gnome_shell_ver}")
# If no GNOME shell version is specified, return the latest version
latest_version = versions_sorted[0]["version"]
return url + f"/{latest_version}/?format=zip"
def main():
"""
Main function
"""
try:
if len(sys.argv) > 4:
print("Too many arguments. Use --help for usage.", file=sys.stderr)
exit(1)
extension_uuid = sys.argv[1]
if extension_uuid in ["-h", "--help"]:
print_help()
exit(0)
except IndexError:
print_help(error=True)
exit(1)
extension_version = sys.argv[2] \
if len(sys.argv) > 2 and sys.argv[2] != "" else None
gnome_shell_version = sys.argv[3] \
if len(sys.argv) > 3 and sys.argv[3] != "" else None
try:
print(get_gnome_extension_download_url(
extension_uuid, extension_version, gnome_shell_version))
exit(0)
except Exception as e:
print(e, file=sys.stderr)
exit(1)
if __name__ == "__main__":
main()

0
tests/__init__.py Normal file
View File

0
tests/fixtures/__init__.py vendored Normal file
View File

207
tests/fixtures/request_values_1.json vendored Normal file
View File

@ -0,0 +1,207 @@
{
"count": 66,
"next": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=2",
"previous": null,
"results": [
{
"extension": "gsconnect@andyholmes.github.io",
"version": 1,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:31.576000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 2,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:31.657000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 3,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:31.749000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 4,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:31.785000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 5,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:31.849000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 6,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:31.881000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 7,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:31.925000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 8,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:31.971000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 9,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:32.036000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 10,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
}
],
"created": "2023-03-26T13:29:32.123000",
"session_modes": []
}
]
}

217
tests/fixtures/request_values_2.json vendored Normal file
View File

@ -0,0 +1,217 @@
{
"count": 66,
"next": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=3",
"previous": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/",
"results": [
{
"extension": "gsconnect@andyholmes.github.io",
"version": 11,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
},
{
"major": 3,
"minor": 28,
"point": -1
}
],
"created": "2023-03-26T13:29:32.209000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 12,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 24,
"point": -1
},
{
"major": 3,
"minor": 26,
"point": -1
},
{
"major": 3,
"minor": 28,
"point": -1
}
],
"created": "2023-03-26T13:29:32.345000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 13,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
}
],
"created": "2023-03-26T13:29:32.443000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 14,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
}
],
"created": "2023-03-26T13:29:32.518000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 15,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
}
],
"created": "2023-03-26T13:29:32.563000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 16,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
}
],
"created": "2023-03-26T13:29:32.624000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 17,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
}
],
"created": "2023-03-26T13:29:32.703000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 18,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
}
],
"created": "2023-03-26T13:29:32.764000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 19,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
}
],
"created": "2023-03-26T13:29:32.832000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 20,
"version_name": null,
"status": 1,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
}
],
"created": "2023-03-26T13:29:32.923000",
"session_modes": []
}
]
}

247
tests/fixtures/request_values_3.json vendored Normal file
View File

@ -0,0 +1,247 @@
{
"count": 66,
"next": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=4",
"previous": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=2",
"results": [
{
"extension": "gsconnect@andyholmes.github.io",
"version": 21,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
},
{
"major": 3,
"minor": 32,
"point": -1
}
],
"created": "2023-03-26T13:29:32.993000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 22,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
},
{
"major": 3,
"minor": 32,
"point": -1
}
],
"created": "2023-03-26T13:29:33.074000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 23,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
},
{
"major": 3,
"minor": 32,
"point": -1
}
],
"created": "2023-03-26T13:29:33.168000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 24,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
},
{
"major": 3,
"minor": 32,
"point": -1
}
],
"created": "2023-03-26T13:29:33.346000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 25,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
},
{
"major": 3,
"minor": 32,
"point": -1
}
],
"created": "2023-03-26T13:29:33.412000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 26,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
},
{
"major": 3,
"minor": 32,
"point": -1
}
],
"created": "2023-03-26T13:29:33.489000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 27,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
},
{
"major": 3,
"minor": 34,
"point": -1
},
{
"major": 3,
"minor": 32,
"point": -1
}
],
"created": "2023-03-26T13:29:33.569000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 28,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 28,
"point": -1
},
{
"major": 3,
"minor": 30,
"point": -1
},
{
"major": 3,
"minor": 34,
"point": -1
},
{
"major": 3,
"minor": 32,
"point": -1
}
],
"created": "2023-03-26T13:29:33.648000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 29,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 34,
"point": -1
}
],
"created": "2023-03-26T13:29:33.732000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 30,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 34,
"point": -1
}
],
"created": "2023-03-26T13:29:33.809000",
"session_modes": []
}
]
}

157
tests/fixtures/request_values_4.json vendored Normal file
View File

@ -0,0 +1,157 @@
{
"count": 66,
"next": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=5",
"previous": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=3",
"results": [
{
"extension": "gsconnect@andyholmes.github.io",
"version": 31,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 34,
"point": -1
}
],
"created": "2023-03-26T13:29:33.889000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 32,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 34,
"point": -1
}
],
"created": "2023-03-26T13:29:33.971000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 33,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 34,
"point": -1
}
],
"created": "2023-03-26T13:29:34.055000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 34,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:34.193000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 35,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:34.278000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 36,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:34.413000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 37,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:34.497000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 38,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:34.625000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 39,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:34.720000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 40,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:34.827000",
"session_modes": []
}
]
}

162
tests/fixtures/request_values_5.json vendored Normal file
View File

@ -0,0 +1,162 @@
{
"count": 66,
"next": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=6",
"previous": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=4",
"results": [
{
"extension": "gsconnect@andyholmes.github.io",
"version": 41,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:34.927000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 42,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:35.233000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 43,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
},
{
"major": 3,
"minor": 38,
"point": -1
}
],
"created": "2023-03-26T13:29:35.313000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 44,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 38,
"point": -1
}
],
"created": "2023-03-26T13:29:35.508000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 45,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 38,
"point": -1
}
],
"created": "2023-03-26T13:29:35.595000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 46,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 40,
"minor": -1,
"point": -1
}
],
"created": "2023-03-26T13:29:35.683000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 47,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 40,
"minor": -1,
"point": -1
}
],
"created": "2023-03-26T13:29:35.772000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 48,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 41,
"minor": -1,
"point": -1
}
],
"created": "2023-03-26T13:29:35.898000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 49,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 41,
"minor": -1,
"point": -1
}
],
"created": "2023-03-26T13:29:36.009000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 50,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 42,
"minor": -1,
"point": -1
}
],
"created": "2023-03-26T13:29:36.110000",
"session_modes": []
}
]
}

167
tests/fixtures/request_values_6.json vendored Normal file
View File

@ -0,0 +1,167 @@
{
"count": 66,
"next": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=7",
"previous": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=5",
"results": [
{
"extension": "gsconnect@andyholmes.github.io",
"version": 51,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 38,
"point": -1
}
],
"created": "2023-03-26T13:29:36.350000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 52,
"version_name": null,
"status": 2,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
},
{
"major": 3,
"minor": 38,
"point": -1
}
],
"created": "2023-03-26T13:29:36.499000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 53,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 3,
"minor": 36,
"point": -1
}
],
"created": "2023-03-26T13:29:36.587000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 54,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 43,
"minor": -1,
"point": -1
}
],
"created": "2023-03-26T13:29:36.649000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 55,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 44,
"minor": -1,
"point": -1
}
],
"created": "2023-03-26T13:29:37.102000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 56,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 45,
"minor": -1,
"point": -1
}
],
"created": "2023-11-05T16:50:37.190000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 57,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 46,
"minor": -1,
"point": -1
}
],
"created": "2024-04-17T06:45:00.793000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 58,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 46,
"minor": -1,
"point": -1
},
{
"major": 47,
"minor": -1,
"point": -1
}
],
"created": "2024-10-24T16:27:57.217000",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 59,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 42,
"minor": -1,
"point": -1
}
],
"created": "2025-03-29T13:51:46.664324",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 60,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 43,
"minor": -1,
"point": -1
}
],
"created": "2025-03-29T13:55:52.914186",
"session_modes": []
}
]
}

117
tests/fixtures/request_values_7.json vendored Normal file
View File

@ -0,0 +1,117 @@
{
"count": 66,
"next": null,
"previous": "https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/?page=6",
"results": [
{
"extension": "gsconnect@andyholmes.github.io",
"version": 61,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 45,
"minor": -1,
"point": -1
}
],
"created": "2025-03-29T13:59:03.367888",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 62,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 46,
"minor": -1,
"point": -1
},
{
"major": 47,
"minor": -1,
"point": -1
},
{
"major": 48,
"minor": -1,
"point": -1
}
],
"created": "2025-03-29T14:07:52.146643",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 63,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 42,
"minor": -1,
"point": -1
}
],
"created": "2025-07-16T18:22:29.769501",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 64,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 43,
"minor": -1,
"point": -1
}
],
"created": "2025-07-16T18:22:53.589928",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 65,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 45,
"minor": -1,
"point": -1
}
],
"created": "2025-07-16T18:23:10.447182",
"session_modes": []
},
{
"extension": "gsconnect@andyholmes.github.io",
"version": 66,
"version_name": null,
"status": 3,
"shell_versions": [
{
"major": 46,
"minor": -1,
"point": -1
},
{
"major": 47,
"minor": -1,
"point": -1
},
{
"major": 48,
"minor": -1,
"point": -1
}
],
"created": "2025-07-16T18:23:25.070141",
"session_modes": []
}
]
}

View File

@ -0,0 +1,16 @@
import json
from pathlib import Path
def get_version_request_values():
version_data = []
fixture_dir = Path(__file__).parent
for file in fixture_dir.glob("request_values_*.json"):
with open(file, "r") as f:
version_data.extend(json.loads(f.read())["results"])
version_data.sort(key=lambda v: v["version"], reverse=True)
return version_data
# Singleton instance to be used in tests
# All extension version information ordered by version descending
values = get_version_request_values()

View File

@ -0,0 +1,47 @@
from src.gnome_extension_download_url.__main__ import check_gnome_shell_version
def test_check_gnome_shell_version_ok():
assert check_gnome_shell_version("3.38", [
{"major": 3, "minor": 36, "point": -1},
{"major": 3, "minor": 37, "point": -1},
{"major": 3, "minor": 38, "point": -1},
])
assert check_gnome_shell_version("3.38.1", [
{"major": 3, "minor": 36, "point": -1},
{"major": 3, "minor": 37, "point": -1},
{"major": 3, "minor": 38, "point": -1},
{"major": 3, "minor": 38, "point": 1},
])
assert check_gnome_shell_version("46", [
{"major": 3, "minor": 36, "point": -1},
{"major": 3, "minor": 37, "point": -1},
{"major": 3, "minor": 38, "point": -1},
{"major": 40, "minor": -1, "point": -1},
{"major": 41, "minor": -1, "point": -1},
{"major": 42, "minor": -1, "point": -1},
{"major": 43, "minor": -1, "point": -1},
{"major": 44, "minor": -1, "point": -1},
{"major": 45, "minor": -1, "point": -1},
{"major": 46, "minor": -1, "point": -1},
])
def test_check_gnome_shell_version_not_ok():
assert not check_gnome_shell_version("3.38", [])
assert not check_gnome_shell_version("3.38.1", [
{"major": 3, "minor": 36, "point": -1},
{"major": 3, "minor": 37, "point": -1},
{"major": 3, "minor": 38, "point": -1},
])
assert not check_gnome_shell_version("46", [
{"major": 3, "minor": 36, "point": -1},
{"major": 3, "minor": 37, "point": -1},
{"major": 3, "minor": 38, "point": -1},
{"major": 40, "minor": -1, "point": -1},
{"major": 41, "minor": -1, "point": -1},
{"major": 42, "minor": -1, "point": -1},
{"major": 43, "minor": -1, "point": -1},
{"major": 44, "minor": -1, "point": -1},
{"major": 45, "minor": -1, "point": -1},
])

View File

@ -0,0 +1,122 @@
import json
import re
from urllib.parse import urlparse, parse_qs
import pytest
import requests
from src.gnome_extension_download_url.__main__ import \
get_gnome_extension_download_url
from tests.fixtures.version_request_values import \
values as version_request_values
class MockResponse:
def __init__(self, *args):
self.status_code = 200
self.url = args[0]
self._path = urlparse(self.url).path
self._query = urlparse(self.url).query
self._extension_version = self._get_extension_version_from_path()
def json(self):
if not self.url:
raise Exception("URL not set yet in MockResponse object.")
# If a specific extension version is requested
if self._extension_version:
version = [v for v in version_request_values if
v["version"] == int(self._extension_version)]
if len(version) > 0:
assert len(version) == 1
return version[0]
# If version not found
else:
self.status_code = 404
return {
"detail": "No ExtensionVersion matches the given query."}
# If no specific version is requested, return paginated results
with open(
f"tests/fixtures/request_values_{self._get_page_parameter()}.json",
"r") as f:
return json.loads(f.read())
def _get_page_parameter(self):
query = urlparse(self.url).query
params = parse_qs(query)
return int(params.get("page", [1])[0])
def _get_extension_version_from_path(self):
version_expression = r"^.+/versions/(\d[\d\.]*)$"
match = re.match(version_expression, self._path)
return match.group(1) if match else None
@pytest.mark.parametrize("ext_uuid, ext_ver, gnome_shell_ver, exception_msg", [
("", None, None, "Extension UUID cannot be empty"),
("extension-uuid", "invalid-version", None,
"Invalid GNOME extension version: invalid-version"),
("extension-uuid", None, "invalid-version",
"Invalid GNOME shell version: invalid-version"),
("extension-uuid", "66", "invalid-version",
"Invalid GNOME shell version: invalid-version"),
("extension-uuid", "invalid-version", "3.38",
"Invalid GNOME extension version: invalid-version"),
("extension-uuid", "-1", None, "Invalid GNOME extension version: -1"),
])
def test_get_gnome_extension_download_url_invalid_args(
ext_uuid, ext_ver, gnome_shell_ver, exception_msg, mocker):
mock_requests_get = mocker.patch("requests.get")
try:
get_gnome_extension_download_url(
ext_uuid, ext_ver, gnome_shell_ver)
except Exception as e:
assert str(e.__str__()) == exception_msg
mock_requests_get.assert_not_called()
@pytest.mark.parametrize("ext_ver, gnome_shell_ver, expected_url", [
(None, None,
"https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/66/?format=zip"),
("latest", None,
"https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/66/?format=zip"),
(None, "latest",
"https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/66/?format=zip"),
("latest", "latest",
"https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/66/?format=zip"),
("43", None,
"https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/43/?format=zip"),
("43", "3.38",
"https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/43/?format=zip"),
("latest", "3.36",
"https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/53/?format=zip"),
("latest", "43",
"https://extensions.gnome.org/api/v1/extensions/gsconnect@andyholmes.github.io/versions/64/?format=zip"),
])
def test_get_gnome_extension_download_url_valid_versions(
ext_ver, gnome_shell_ver, expected_url, monkeypatch):
monkeypatch.setattr(requests, "get", MockResponse)
download_url = get_gnome_extension_download_url(
"gsconnect@andyholmes.github.io", ext_ver, gnome_shell_ver)
assert download_url == expected_url
@pytest.mark.parametrize("ext_ver, gnome_shell_ver, expected_result", [
(0, None, "Extension 'gsconnect@andyholmes.github.io' version 0 not found"),
("66", "0",
"Extension version 66 is not compatible with GNOME shell version 0"),
("latest", "0", "No compatible version found for GNOME shell version 0"),
])
def test_get_gnome_extension_download_url_invalid_versions(
ext_ver, gnome_shell_ver, expected_result, monkeypatch):
monkeypatch.setattr(requests, "get", MockResponse)
try:
get_gnome_extension_download_url(
"gsconnect@andyholmes.github.io", ext_ver, gnome_shell_ver)
except Exception as e:
assert str(e) == expected_result

106
tests/test_main.py Normal file
View File

@ -0,0 +1,106 @@
import pytest
from src.gnome_extension_download_url.__main__ import main, HELP_MSG
def test_main_empty_args(capsys, mocker):
mocker.patch("sys.argv", ["/path/to/gnome-extension-download-url"])
try:
main()
except SystemExit as e:
assert e.code == 1
captured = capsys.readouterr()
assert captured.err == f"{HELP_MSG}\n"
def test_main_help(capsys, mocker):
mocker.patch("sys.argv", ["/path/to/gnome-extension-download-url", "--help"])
try:
main()
except SystemExit as e:
assert e.code == 0
captured = capsys.readouterr()
assert captured.out == f"{HELP_MSG}\n"
def test_main_too_many_arguments(capsys, mocker):
mocker.patch("sys.argv", [
"/path/to/gnome-extension-download-url",
"1", "2", "3", "4"
])
try:
main()
except SystemExit as e:
assert e.code == 1
captured = capsys.readouterr()
assert captured.err == "Too many arguments. Use --help for usage.\n"
def test_main_valid_args_only_extension_uuid(capsys, mocker):
mock_get_gnome_extension_download_url = mocker.patch(
"src.gnome_extension_download_url.__main__.get_gnome_extension_download_url",
return_value="http://example.com/download")
mocker.patch("sys.argv", [
"/path/to/gnome-extension-download-url",
"extension-uuid",
])
try:
main()
except SystemExit as e:
assert e.code == 0
captured = capsys.readouterr()
assert captured.out == "http://example.com/download\n"
mock_get_gnome_extension_download_url.assert_called_once_with(
"extension-uuid", None, None)
@pytest.mark.parametrize("extension_version", ["", "latest", "66"])
def test_main_valid_args_extension_uuid_and_extension_version(
extension_version, capsys, mocker):
mock_get_gnome_extension_download_url= mocker.patch(
"src.gnome_extension_download_url.__main__.get_gnome_extension_download_url",
return_value="http://example.com/download")
mocker.patch("sys.argv", [
"/path/to/gnome-extension-download-url",
"extension-uuid",
extension_version,
])
try:
main()
except SystemExit as e:
assert e.code == 0
captured = capsys.readouterr()
assert captured.out == "http://example.com/download\n"
mock_get_gnome_extension_download_url.assert_called_once_with(
"extension-uuid", extension_version if extension_version != "" else None,
None)
@pytest.mark.parametrize("extension_version, gnome_shell_version",
[
("", ""), ("", "latest"), ("", "66"),
("latest", ""), ("latest", "latest"),
("latest", "3.38"),
("66", ""), ("66", "latest"), ("66", "46")
])
def test_main_valid_args_extension_uuid_extension_version_and_gnome_shell_version(
extension_version, gnome_shell_version, capsys, mocker):
mock_get_gnome_extension_download_url= mocker.patch(
"src.gnome_extension_download_url.__main__.get_gnome_extension_download_url",
return_value="http://example.com/download")
mocker.patch("sys.argv", [
"/path/to/gnome-extension-download-url",
"extension-uuid",
extension_version,
gnome_shell_version,
])
try:
main()
except SystemExit as e:
assert e.code == 0
captured = capsys.readouterr()
assert captured.out == "http://example.com/download\n"
mock_get_gnome_extension_download_url.assert_called_once_with(
"extension-uuid",
extension_version if extension_version != "" else None,
gnome_shell_version if gnome_shell_version != "" else None)

15
tests/test_print_help.py Normal file
View File

@ -0,0 +1,15 @@
from src.gnome_extension_download_url.__main__ import print_help, HELP_MSG
def test_print_help_no_error(capsys):
print_help()
captured = capsys.readouterr()
assert captured.out == f"{HELP_MSG}\n"
assert captured.err == ""
def test_print_help_error(capsys):
print_help(error=True)
captured = capsys.readouterr()
assert captured.err == f"{HELP_MSG}\n"
assert captured.out == ""

172
uv.lock Normal file
View File

@ -0,0 +1,172 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "certifi"
version = "2025.8.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" },
{ url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" },
{ url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" },
{ url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" },
{ url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" },
{ url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" },
{ url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" },
{ url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" },
{ url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" },
{ url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" },
{ url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" },
{ url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" },
{ url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" },
{ url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" },
{ url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" },
{ url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" },
{ url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" },
{ url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" },
{ url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" },
{ url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" },
{ url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" },
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "gnome-extension-download-url"
version = "1.0.0"
source = { editable = "." }
dependencies = [
{ name = "requests" },
]
[package.dev-dependencies]
dev = [
{ name = "pytest" },
{ name = "pytest-mock" },
]
[package.metadata]
requires-dist = [{ name = "requests", specifier = ">=2.32.5" }]
[package.metadata.requires-dev]
dev = [
{ name = "pytest", specifier = ">=8.4.2" },
{ name = "pytest-mock", specifier = ">=3.15.0" },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "iniconfig"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "pytest"
version = "8.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
]
[[package]]
name = "pytest-mock"
version = "3.15.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/61/99/3323ee5c16b3637b4d941c362182d3e749c11e400bea31018c42219f3a98/pytest_mock-3.15.0.tar.gz", hash = "sha256:ab896bd190316b9d5d87b277569dfcdf718b2d049a2ccff5f7aca279c002a1cf", size = 33838, upload-time = "2025-09-04T20:57:48.679Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/b3/7fefc43fb706380144bcd293cc6e446e6f637ddfa8b83f48d1734156b529/pytest_mock-3.15.0-py3-none-any.whl", hash = "sha256:ef2219485fb1bd256b00e7ad7466ce26729b30eadfc7cbcdb4fa9a92ca68db6f", size = 10050, upload-time = "2025-09-04T20:57:47.274Z" },
]
[[package]]
name = "requests"
version = "2.32.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "urllib3"
version = "2.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
]