Merge pull request #482 from arabcoders/dev
Fix: handle errors when fetching remote thumbnails
This commit is contained in:
commit
8ff6582316
14 changed files with 247 additions and 47 deletions
44
FAQ.md
44
FAQ.md
|
|
@ -47,6 +47,7 @@ or the `environment:` section in `compose.yaml` file.
|
|||
| YTP_ALLOW_INTERNAL_URLS | Allow requests to internal URLs | `false` |
|
||||
| YTP_SIMPLE_MODE | Switch default interface to Simple mode. | `false` |
|
||||
| YTP_STATIC_UI_PATH | Path to custom static UI files. | `(not_set)` |
|
||||
| YTP_AUTO_CLEAR_HISTORY_DAYS | Number of days after which completed download history is cleared. | `0` |
|
||||
|
||||
> [!NOTE]
|
||||
> To raise the maximum workers for specific extractor, you need to add a ENV variable that follows the pattern `YTP_MAX_WORKERS_FOR_<EXTRACTOR_NAME>`.
|
||||
|
|
@ -54,8 +55,12 @@ or the `environment:` section in `compose.yaml` file.
|
|||
> The limit should not exceed the `YTP_MAX_WORKERS` value as it will be ignored.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The env variable `YTP_SIMPLE_MODE` only control what being displayed for first time visitor, the users can still switch between the two modes via the WebUI settings page.
|
||||
> The env variable `YTP_SIMPLE_MODE` only control what being displayed for first time visitor, the users can still switch between the two modes via the WebUI settings page.
|
||||
|
||||
## Notes about YTP_AUTO_CLEAR_HISTORY_DAYS
|
||||
|
||||
- `0` days means no automatic clearing of the download history. lowest value that will trigger the clearing is `1` day.
|
||||
- This setting will **NOT** delete the downloaded files, it will only clear the history from the database.
|
||||
|
||||
# Browser extensions & bookmarklets
|
||||
|
||||
|
|
@ -507,3 +512,40 @@ Thats it, the `main.yml` will now disable the docker/github container registries
|
|||
naming, your container name will be named `REGISTRY/ytptube` and the tags will be the same as the ones used in the github registry.
|
||||
|
||||
Unfortunately, the `native-builder.yml` workflow doesn't support self-hosted repositories at the moment.
|
||||
|
||||
# Getting No space left on device error
|
||||
|
||||
If you encounter this error: `OSError: [Errno 28] No space left on device` This indicates that either
|
||||
the `/tmp` or `/downloads` directory has run out of available space.
|
||||
|
||||
This issue commonly occurs when:
|
||||
|
||||
- `/tmp` is mounted as `tmpfs` (memory-based storage)
|
||||
- Your system has limited RAM
|
||||
- You're downloading large video files
|
||||
|
||||
Since videos are temporarily stored in `/tmp` before being moved to the final download location, memory-based storage
|
||||
may be insufficient for large downloads.
|
||||
|
||||
To fix the issue, modify your `compose.yaml` to use a disk-based directory for temporary files:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
ytptube:
|
||||
user: "${UID:-1000}:${UID:-1000}"
|
||||
image: ghcr.io/arabcoders/ytptube:latest
|
||||
container_name: ytptube
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8081:8081"
|
||||
volumes:
|
||||
- ./config:/config:rw
|
||||
- ./downloads:/downloads/local:rw
|
||||
- ./temp:/tmp:rw
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Replace the `tmpfs` mount with a local directory volume (`./temp:/tmp:rw`). This allows temporary files to use disk space instead of RAM.
|
||||
|
||||
After making the changes, restart your container. This should resolve the "No space left on device"
|
||||
error during download.
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ services:
|
|||
|
||||
> [!IMPORTANT]
|
||||
> Make sure to change the `user` line to match your user id and group id
|
||||
> If you have low RAM, remove the `tmpfs` and mount a disk-based directory to `/tmp` instead. See [FAQ](FAQ.md#getting-no-space-left-on-device-error) for more information.
|
||||
|
||||
```bash
|
||||
$ mkdir -p ./{config,downloads} && docker compose -f compose.yaml up -d
|
||||
|
|
|
|||
|
|
@ -140,6 +140,15 @@ class DownloadQueue(metaclass=Singleton):
|
|||
func=self._check_live,
|
||||
id=f"{__class__.__name__}.{__class__._check_live.__name__}",
|
||||
)
|
||||
|
||||
if self.config.auto_clear_history_days > 0:
|
||||
Scheduler.get_instance().add(
|
||||
# timer="8 */1 * * *",
|
||||
timer="* * * * *",
|
||||
func=self._delete_old_history,
|
||||
id=f"{__class__.__name__}.{__class__._delete_old_history.__name__}",
|
||||
)
|
||||
|
||||
# app.on_shutdown.append(self.on_shutdown)
|
||||
|
||||
async def test(self) -> bool:
|
||||
|
|
@ -1262,6 +1271,48 @@ class DownloadQueue(metaclass=Singleton):
|
|||
LOG.exception(e)
|
||||
LOG.error(f"Failed to retry item '{item_ref}'. {e!s}")
|
||||
|
||||
async def _delete_old_history(self) -> None:
|
||||
"""
|
||||
Automatically delete old download history based on user specified days.
|
||||
0 or negative value disables this feature.
|
||||
"""
|
||||
if self.config.auto_clear_history_days < 0 or self.is_paused() or self.done.empty():
|
||||
return
|
||||
|
||||
cutoff_date: datetime = datetime.now(UTC) - timedelta(days=self.config.auto_clear_history_days)
|
||||
items_to_delete: list[tuple[str, ItemDTO]] = []
|
||||
|
||||
for key, download in self.done.items():
|
||||
info: ItemDTO = download.info
|
||||
if not info or not info.timestamp:
|
||||
continue
|
||||
|
||||
if "finished" != info.status or not info.filename:
|
||||
continue
|
||||
|
||||
try:
|
||||
timestamp_seconds: float = info.timestamp / 1e9
|
||||
item_datetime: datetime = datetime.fromtimestamp(timestamp_seconds, tz=UTC)
|
||||
if item_datetime < cutoff_date:
|
||||
items_to_delete.append((key, info))
|
||||
except (OSError, ValueError, OverflowError) as e:
|
||||
LOG.error(f"Failed to parse timestamp '{info.timestamp}' for item '{info.title}': {e}")
|
||||
|
||||
titles: list[str] = []
|
||||
for key, info in items_to_delete:
|
||||
item_name: str = info.title or info.id or info._id
|
||||
self._notify.emit(
|
||||
Events.ITEM_DELETED,
|
||||
data=info,
|
||||
title="Download Cleared",
|
||||
message=f"'{item_name}' record removed from history.",
|
||||
)
|
||||
titles.append(item_name)
|
||||
self.done.delete(key)
|
||||
|
||||
if titles:
|
||||
LOG.info(f"Automatically cleared '{', '.join(titles)}' from download history due to age.")
|
||||
|
||||
def _handle_task_exception(self, task: asyncio.Task) -> None:
|
||||
if task.cancelled():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ class Events:
|
|||
|
||||
CONNECTED: str = "connected"
|
||||
|
||||
CONFIGURATION: str = "configuration"
|
||||
|
||||
LOG_INFO: str = "log_info"
|
||||
LOG_WARNING: str = "log_warning"
|
||||
LOG_ERROR: str = "log_error"
|
||||
|
|
@ -90,6 +92,7 @@ class Events:
|
|||
|
||||
"""
|
||||
return [
|
||||
Events.CONFIGURATION,
|
||||
Events.CONNECTED,
|
||||
Events.LOG_INFO,
|
||||
Events.LOG_WARNING,
|
||||
|
|
@ -97,7 +100,6 @@ class Events:
|
|||
Events.LOG_SUCCESS,
|
||||
Events.ITEM_ADDED,
|
||||
Events.ITEM_UPDATED,
|
||||
Events.ITEM_COMPLETED,
|
||||
Events.ITEM_CANCELLED,
|
||||
Events.ITEM_DELETED,
|
||||
Events.ITEM_MOVED,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class HttpSocket:
|
|||
self.sio = sio or socketio.AsyncServer(
|
||||
async_handlers=True,
|
||||
async_mode="aiohttp",
|
||||
cors_allowed_origins=[],
|
||||
cors_allowed_origins="*",
|
||||
transports=["websocket", "polling"],
|
||||
logger=self.config.debug,
|
||||
engineio_logger=self.config.debug,
|
||||
|
|
|
|||
|
|
@ -482,6 +482,9 @@ def check_id(file: Path) -> bool | str:
|
|||
id: str | None = match.groupdict().get("id")
|
||||
|
||||
try:
|
||||
if not file.parent.exists():
|
||||
return False
|
||||
|
||||
for f in file.parent.iterdir():
|
||||
if id not in f.stem:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -192,6 +192,9 @@ class Config(metaclass=Singleton):
|
|||
playlist_items_concurrency: int = 4
|
||||
"""The number of concurrent playlist items to be processed at same time."""
|
||||
|
||||
auto_clear_history_days: int = 0
|
||||
"""Number of days after which completed download history is automatically cleared. 0 to disable."""
|
||||
|
||||
pictures_backends: list[str] = [
|
||||
"https://unsplash.it/1920/1080?random",
|
||||
"https://picsum.photos/1920/1080",
|
||||
|
|
@ -230,6 +233,7 @@ class Config(metaclass=Singleton):
|
|||
"playlist_items_concurrency",
|
||||
"download_path_depth",
|
||||
"download_info_expires",
|
||||
"auto_clear_history_days",
|
||||
)
|
||||
"The variables that are integers."
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,12 @@ async def get_thumbnail(request: Request, config: Config) -> Response:
|
|||
LOG.debug(f"Fetching thumbnail from '{url}'.")
|
||||
response = await client.request(method="GET", url=url, follow_redirects=True)
|
||||
|
||||
if response.status_code != web.HTTPOk.status_code:
|
||||
LOG.error(f"Failed to fetch thumbnail from '{url}'. Status code: {response.status_code}.")
|
||||
return web.json_response(
|
||||
data={"error": "failed to retrieve the thumbnail."}, status=response.status_code
|
||||
)
|
||||
|
||||
return web.Response(
|
||||
body=response.content,
|
||||
headers={
|
||||
|
|
|
|||
|
|
@ -23,25 +23,31 @@ class _Data:
|
|||
|
||||
@route(RouteType.SOCKET, "connect", "socket_connect")
|
||||
async def connect(config: Config, queue: DownloadQueue, notify: EventBus, sid: str):
|
||||
data = {
|
||||
**queue.get(),
|
||||
"config": config.frontend(),
|
||||
"presets": Presets.get_instance().get_all(),
|
||||
"dl_fields": DLFields.get_instance().get_all(),
|
||||
"paused": queue.is_paused(),
|
||||
}
|
||||
|
||||
data["folders"] = list_folders(
|
||||
path=Path(config.download_path),
|
||||
base=Path(config.download_path),
|
||||
depth_limit=config.download_path_depth-1,
|
||||
notify.emit(
|
||||
Events.CONFIGURATION,
|
||||
data={
|
||||
"config": config.frontend(),
|
||||
"presets": Presets.get_instance().get_all(),
|
||||
"dl_fields": DLFields.get_instance().get_all(),
|
||||
"paused": queue.is_paused(),
|
||||
},
|
||||
title="Client connected",
|
||||
message=f"Client '{sid}' connected.",
|
||||
to=sid,
|
||||
)
|
||||
|
||||
notify.emit(
|
||||
Events.CONNECTED,
|
||||
data=data,
|
||||
title="Client connected",
|
||||
message=f"Client '{sid}' connected.",
|
||||
data={
|
||||
"folders": list_folders(
|
||||
path=Path(config.download_path),
|
||||
base=Path(config.download_path),
|
||||
depth_limit=config.download_path_depth - 1,
|
||||
),
|
||||
**queue.get(),
|
||||
},
|
||||
title="Sending initial download data",
|
||||
message=f"Sending initial download data to client '{sid}'.",
|
||||
to=sid,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,6 @@ class TestEvents:
|
|||
Events.LOG_SUCCESS,
|
||||
Events.ITEM_ADDED,
|
||||
Events.ITEM_UPDATED,
|
||||
Events.ITEM_COMPLETED,
|
||||
]
|
||||
|
||||
for expected in expected_frontend:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ dependencies = [
|
|||
"aiocron>=1.8",
|
||||
"python-dotenv>=1.0.1",
|
||||
"debugpy>=1.8.1",
|
||||
"httpx",
|
||||
"httpx[brotli,http2,socks,zstd]",
|
||||
"async-timeout",
|
||||
"pyjson5",
|
||||
"curl_cffi>=0.13",
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@
|
|||
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item has-dropdown" v-if="config.app.file_logging || config.app.console_enabled">
|
||||
<div class="navbar-item has-dropdown" v-if="config.app?.file_logging || config.app?.console_enabled">
|
||||
<a class="navbar-link" @click="(e: MouseEvent) => openMenu(e)">
|
||||
<span class="icon"><i class="fas fa-tools" /></span>
|
||||
<span>Other</span>
|
||||
|
|
@ -111,7 +111,7 @@
|
|||
|
||||
<div class="navbar-dropdown">
|
||||
<NuxtLink class="navbar-item" to="/logs" @click.prevent="(e: MouseEvent) => changeRoute(e)"
|
||||
v-if="config.app.file_logging">
|
||||
v-if="config.app?.file_logging">
|
||||
<span class="icon"><i class="fa-solid fa-file-lines" /></span>
|
||||
<span>Logs</span>
|
||||
</NuxtLink>
|
||||
|
|
|
|||
|
|
@ -51,11 +51,13 @@ export const useSocketStore = defineStore('socket', () => {
|
|||
|
||||
const connect = () => {
|
||||
const opts = {
|
||||
transports: ['websocket', 'polling'],
|
||||
transports: ['polling', 'websocket'],
|
||||
withCredentials: true,
|
||||
reconnection: true,
|
||||
reconnectionAttempts: 30,
|
||||
reconnectionDelay: 5000
|
||||
reconnectionAttempts: 50,
|
||||
reconnectionDelay: 5000,
|
||||
tryAllTransports: true,
|
||||
timeout: 10000 * 5,
|
||||
} as Partial<ManagerOptions & SocketOptions>
|
||||
|
||||
let url = runtimeConfig.public.wss
|
||||
|
|
@ -70,28 +72,32 @@ export const useSocketStore = defineStore('socket', () => {
|
|||
connectionStatus.value = 'connecting';
|
||||
socket.value = io(url, opts)
|
||||
|
||||
socket.value.on('connect', () => {
|
||||
on("connect_error", (e: any) => console.error("Socket connection error:", e));
|
||||
|
||||
on('connect', () => {
|
||||
isConnected.value = true
|
||||
connectionStatus.value = 'connected';
|
||||
});
|
||||
|
||||
socket.value.on('disconnect', () => {
|
||||
on('disconnect', () => {
|
||||
isConnected.value = false
|
||||
connectionStatus.value = 'disconnected';
|
||||
});
|
||||
|
||||
socket.value.on('connected', stream => {
|
||||
on('configuration', stream => {
|
||||
const json = JSON.parse(stream)
|
||||
|
||||
config.setAll({
|
||||
app: json.data.config,
|
||||
tasks: json.data.tasks,
|
||||
folders: json.data.folders,
|
||||
presets: json.data.presets,
|
||||
dl_fields: json.data.dl_fields,
|
||||
paused: Boolean(json.data.paused)
|
||||
} as Partial<ConfigState>)
|
||||
})
|
||||
|
||||
on('connected', stream => {
|
||||
const json = JSON.parse(stream);
|
||||
config.add('folders', json.data.folders)
|
||||
stateStore.addAll('queue', json.data.queue || {})
|
||||
stateStore.addAll('history', json.data.done || {})
|
||||
})
|
||||
|
|
@ -122,21 +128,6 @@ export const useSocketStore = defineStore('socket', () => {
|
|||
}
|
||||
}, true);
|
||||
|
||||
on('item_completed', (stream: string) => {
|
||||
const json = JSON.parse(stream);
|
||||
|
||||
if (true === stateStore.has('queue', json.data._id)) {
|
||||
stateStore.remove('queue', json.data._id);
|
||||
}
|
||||
|
||||
if (true === stateStore.has('history', json.data._id)) {
|
||||
stateStore.update('history', json.data._id, json.data);
|
||||
return;
|
||||
}
|
||||
|
||||
stateStore.add('history', json.data._id, json.data);
|
||||
});
|
||||
|
||||
on('item_cancelled', (stream: string) => {
|
||||
const item = JSON.parse(stream);
|
||||
const id = item.data._id
|
||||
|
|
|
|||
99
uv.lock
99
uv.lock
|
|
@ -524,6 +524,28 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "4.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "hpack" },
|
||||
{ name = "hyperframe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hpack"
|
||||
version = "4.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
|
|
@ -552,6 +574,21 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
brotli = [
|
||||
{ name = "brotli", marker = "platform_python_implementation == 'CPython'" },
|
||||
{ name = "brotlicffi", marker = "platform_python_implementation != 'CPython'" },
|
||||
]
|
||||
http2 = [
|
||||
{ name = "h2" },
|
||||
]
|
||||
socks = [
|
||||
{ name = "socksio" },
|
||||
]
|
||||
zstd = [
|
||||
{ name = "zstandard" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-curl-cffi"
|
||||
version = "0.1.4"
|
||||
|
|
@ -578,6 +615,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyperframe"
|
||||
version = "6.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
|
|
@ -1490,6 +1536,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socksio"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sortedcontainers"
|
||||
version = "2.4.0"
|
||||
|
|
@ -1710,7 +1765,7 @@ dependencies = [
|
|||
{ name = "dateparser" },
|
||||
{ name = "debugpy" },
|
||||
{ name = "defusedxml" },
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx", extra = ["brotli", "http2", "socks", "zstd"] },
|
||||
{ name = "httpx-curl-cffi" },
|
||||
{ name = "jmespath" },
|
||||
{ name = "jsonschema" },
|
||||
|
|
@ -1761,7 +1816,7 @@ requires-dist = [
|
|||
{ name = "dateparser", specifier = ">=1.2.1" },
|
||||
{ name = "debugpy", specifier = ">=1.8.1" },
|
||||
{ name = "defusedxml", specifier = ">=0.7.1" },
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx", extras = ["brotli", "http2", "socks", "zstd"] },
|
||||
{ name = "httpx-curl-cffi", specifier = ">=0.1.4" },
|
||||
{ name = "jmespath", specifier = ">=1.0.1" },
|
||||
{ name = "jsonschema", specifier = ">=4.23.0" },
|
||||
|
|
@ -1801,3 +1856,43 @@ sdist = { url = "https://files.pythonhosted.org/packages/11/f2/690a35762cf8366ce
|
|||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/62/c2da1c495291a52e561257d017585e08906d288035d025ccf636f6b9a266/zipstream_ng-1.9.0-py3-none-any.whl", hash = "sha256:31dc2cf617abdbf28d44f2e08c0d14c8eee2ea0ec26507a7e4d5d5f97c564b7a", size = 24852 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstandard"
|
||||
version = "0.25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889 },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751 },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818 },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108 },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940 },
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue