mirror of
https://github.com/codex-storage/bittorrent-benchmarks.git
synced 2025-01-24 18:08:50 +00:00
simplify module structure, wrap up config, fix bugs
This commit is contained in:
parent
42cd2e7b1c
commit
a286dc5e2a
46
benchmarks/core/config.py
Normal file
46
benchmarks/core/config.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""Basic utilities for structuring experiment configurations based on Pydantic schemas."""
|
||||
|
||||
import re
|
||||
from abc import abstractmethod
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import BaseModel, IPvAnyAddress, AfterValidator
|
||||
from typing_extensions import Generic
|
||||
|
||||
from benchmarks.core.experiments.experiment import TExperiment
|
||||
|
||||
|
||||
def drop_config_suffix(name: str) -> str:
|
||||
return name[:-6] if name.endswith('Config') else name
|
||||
|
||||
|
||||
class ConfigModel(BaseModel):
|
||||
model_config = {
|
||||
'alias_generator': drop_config_suffix
|
||||
}
|
||||
|
||||
|
||||
# This is a simple regex which is not by any means exhaustive but should cover gross syntax errors.
|
||||
VALID_DOMAIN_NAME = re.compile(r"^localhost$|^(?!-)([A-Za-z0-9-]+\.)+[A-Za-z]{2,6}$")
|
||||
|
||||
|
||||
def is_valid_domain_name(domain_name: str):
|
||||
stripped = domain_name.strip()
|
||||
matches = VALID_DOMAIN_NAME.match(stripped)
|
||||
assert matches is not None
|
||||
return stripped
|
||||
|
||||
|
||||
DomainName = Annotated[str, AfterValidator(is_valid_domain_name)]
|
||||
|
||||
|
||||
class Host(BaseModel):
|
||||
address: IPvAnyAddress | DomainName
|
||||
|
||||
|
||||
class ExperimentBuilder(Generic[TExperiment], ConfigModel):
|
||||
""":class:`ExperimentBuilders` can build real :class:`Experiment`s out of :class:`ConfigModel`s. """
|
||||
|
||||
@abstractmethod
|
||||
def build(self) -> TExperiment:
|
||||
pass
|
17
benchmarks/core/experiments/experiment.py
Normal file
17
benchmarks/core/experiments/experiment.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""Basic definitions for structuring experiments."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from mypy.graph_utils import TypeVar
|
||||
|
||||
|
||||
class Experiment(ABC):
|
||||
"""An :class:`Experiment` is an arbitrary piece of code that can be run and measured."""
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
"""Synchronously runs the experiment, blocking the current thread until it's done."""
|
||||
pass
|
||||
|
||||
|
||||
TExperiment = TypeVar('TExperiment', bound=Experiment)
|
@ -1,10 +1,11 @@
|
||||
from typing_extensions import Generic, List
|
||||
|
||||
from benchmarks.core.experiments.experiment import Experiment
|
||||
from benchmarks.core.network import TInitialMetadata, TNetworkHandle, Node
|
||||
from benchmarks.core.utils import ExperimentData
|
||||
|
||||
|
||||
class StaticDisseminationExperiment(Generic[TNetworkHandle, TInitialMetadata]):
|
||||
class StaticDisseminationExperiment(Generic[TNetworkHandle, TInitialMetadata], Experiment):
|
||||
def __init__(
|
||||
self,
|
||||
network: List[Node[TNetworkHandle, TInitialMetadata]],
|
||||
|
@ -3,7 +3,7 @@ from ipaddress import IPv4Address, IPv6Address
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from benchmarks.deluge.config.host import Host, DomainName
|
||||
from benchmarks.core.config import Host, DomainName
|
||||
|
||||
|
||||
def test_should_parse_ipv4_address():
|
||||
@ -26,6 +26,14 @@ def test_should_parse_localhost():
|
||||
assert h.address == DomainName('localhost')
|
||||
|
||||
|
||||
def test_should_return_correct_string_representation_for_addresses():
|
||||
h = Host(address='localhost')
|
||||
assert str(h.address) == 'localhost'
|
||||
|
||||
h = Host(address='192.168.1.1')
|
||||
assert str(h.address) == '192.168.1.1'
|
||||
|
||||
|
||||
def test_should_fail_invalid_names():
|
||||
invalid_names = [
|
||||
'-node-1.local.svc',
|
@ -65,7 +65,8 @@ def sample(n: int) -> Iterator[int]:
|
||||
for i in range(n - 1):
|
||||
j = i + random.randint(0, n - i)
|
||||
tmp = p[j]
|
||||
p[j], p[j + 1] = p[j + 1], tmp
|
||||
p[j] = p[i]
|
||||
p[i] = tmp
|
||||
yield p[i]
|
||||
|
||||
|
||||
|
@ -2,58 +2,65 @@ from itertools import islice
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
from pydantic import BaseModel, Field, model_validator, HttpUrl
|
||||
from torrentool.torrent import Torrent
|
||||
from urllib3.util import parse_url
|
||||
|
||||
from benchmarks.core.config import Host, ExperimentBuilder
|
||||
from benchmarks.core.experiments.static_experiment import StaticDisseminationExperiment
|
||||
from benchmarks.core.utils import sample
|
||||
from benchmarks.deluge.config.host import Host
|
||||
from benchmarks.core.utils import sample, RandomTempData
|
||||
from benchmarks.deluge.deluge_node import DelugeMeta, DelugeNode as RealDelugeNode
|
||||
|
||||
|
||||
class DelugeNode(BaseModel):
|
||||
class DelugeNodeConfig(BaseModel):
|
||||
address: Host
|
||||
daemon_port: int
|
||||
listen_ports: list[int] = Field(min_length=2, max_length=2)
|
||||
|
||||
|
||||
class DelugeNodeSet(BaseModel):
|
||||
class DelugeNodeSetConfig(BaseModel):
|
||||
network_size: int = Field(gt=2)
|
||||
address: str
|
||||
daemon_port: int
|
||||
listen_ports: list[int] = Field(min_length=2, max_length=2)
|
||||
nodes: List[DelugeNode] = []
|
||||
nodes: List[DelugeNodeConfig] = []
|
||||
|
||||
@model_validator(mode='after')
|
||||
def expand_nodes(self):
|
||||
self.nodes = [
|
||||
DelugeNode(
|
||||
DelugeNodeConfig(
|
||||
address=Host(address=self.address.format(node_index=str(i))),
|
||||
daemon_port=self.daemon_port,
|
||||
listen_ports=self.listen_ports,
|
||||
)
|
||||
for i in range(1, self.network_size + 1)
|
||||
]
|
||||
return self
|
||||
|
||||
|
||||
class DelugeExperiment(BaseModel):
|
||||
DelugeDisseminationExperiment = StaticDisseminationExperiment[Torrent, DelugeMeta]
|
||||
|
||||
|
||||
class DelugeExperimentConfig(ExperimentBuilder[DelugeDisseminationExperiment]):
|
||||
file_size: int = Field(gt=0)
|
||||
repetitions: int = Field(gt=0)
|
||||
seeders: int = Field(gt=0)
|
||||
shared_volume_path: Path
|
||||
nodes: List[DelugeNode] | DelugeNodeSet
|
||||
tracker_announce_url: HttpUrl
|
||||
nodes: List[DelugeNodeConfig] | DelugeNodeSetConfig
|
||||
|
||||
def build(self) -> StaticDisseminationExperiment[Torrent, DelugeMeta]:
|
||||
def build(self) -> DelugeDisseminationExperiment:
|
||||
nodes = self.nodes.nodes if isinstance(self.nodes, DelugeNodeSetConfig) else self.nodes
|
||||
return StaticDisseminationExperiment(
|
||||
network=[
|
||||
RealDelugeNode(
|
||||
name=f'deluge-{i}',
|
||||
volume=self.shared_volume_path / f'deluge-{i}',
|
||||
daemon_port=node.daemon_port,
|
||||
daemon_address=node.address,
|
||||
daemon_address=str(node.address.address),
|
||||
)
|
||||
for i, node in enumerate(self.nodes)
|
||||
for i, node in enumerate(nodes)
|
||||
],
|
||||
seeders=list(islice(sample(len(self.nodes)), self.seeders)),
|
||||
data=self.data
|
||||
seeders=list(islice(sample(len(nodes)), self.seeders)),
|
||||
data=RandomTempData(size=self.file_size,
|
||||
meta=DelugeMeta('dataset-1', announce_url=parse_url(str(self.tracker_announce_url))))
|
||||
)
|
@ -1,21 +0,0 @@
|
||||
import re
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import BaseModel, StringConstraints, IPvAnyAddress, AfterValidator
|
||||
|
||||
# This is a simple regex which is not by any means exhaustive but should cover gross syntax errors.
|
||||
VALID_DOMAIN_NAME = re.compile(r"^localhost$|^(?!-)([A-Za-z0-9-]+\.)+[A-Za-z]{2,6}$")
|
||||
|
||||
|
||||
def is_valid_domain_name(domain_name: str):
|
||||
stripped = domain_name.strip()
|
||||
matches = VALID_DOMAIN_NAME.match(stripped)
|
||||
assert matches is not None
|
||||
return stripped
|
||||
|
||||
|
||||
DomainName = Annotated[str, AfterValidator(is_valid_domain_name)]
|
||||
|
||||
|
||||
class Host(BaseModel):
|
||||
address: IPvAnyAddress | DomainName
|
@ -1,36 +0,0 @@
|
||||
from io import StringIO
|
||||
|
||||
from benchmarks.deluge.config.deluge import DelugeNodeSet, DelugeNode
|
||||
from benchmarks.deluge.config.host import Host
|
||||
|
||||
|
||||
def test_should_expand_node_sets_into_simple_nodes():
|
||||
nodeset = DelugeNodeSet(
|
||||
address='deluge-{node_index}.local.svc',
|
||||
network_size=4,
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082]
|
||||
)
|
||||
|
||||
assert nodeset.nodes == [
|
||||
DelugeNode(
|
||||
address=Host(address='deluge-1.local.svc'),
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082],
|
||||
),
|
||||
DelugeNode(
|
||||
address=Host(address='deluge-2.local.svc'),
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082],
|
||||
),
|
||||
DelugeNode(
|
||||
address=Host(address='deluge-3.local.svc'),
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082],
|
||||
),
|
||||
DelugeNode(
|
||||
address=Host(address='deluge-4.local.svc'),
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082],
|
||||
),
|
||||
]
|
@ -52,8 +52,6 @@ class DelugeNode(SharedFSNode[Torrent, DelugeMeta]):
|
||||
|
||||
super().__init__(self.downloads_root)
|
||||
|
||||
self._init_folders()
|
||||
|
||||
def wipe_all_torrents(self):
|
||||
torrent_ids = list(self.rpc.core.get_torrents_status({}, []).keys())
|
||||
if torrent_ids:
|
||||
@ -109,6 +107,7 @@ class DelugeNode(SharedFSNode[Torrent, DelugeMeta]):
|
||||
@property
|
||||
def rpc(self) -> DelugeRPCClient:
|
||||
if self._rpc is None:
|
||||
self._init_folders()
|
||||
self.connect()
|
||||
return self._rpc
|
||||
|
||||
|
61
benchmarks/deluge/tests/test_config.py
Normal file
61
benchmarks/deluge/tests/test_config.py
Normal file
@ -0,0 +1,61 @@
|
||||
from io import StringIO
|
||||
|
||||
import yaml
|
||||
|
||||
from benchmarks.core.config import Host
|
||||
from benchmarks.deluge.config import DelugeNodeSetConfig, DelugeNodeConfig, DelugeExperimentConfig
|
||||
|
||||
|
||||
def test_should_expand_node_sets_into_simple_nodes():
|
||||
nodeset = DelugeNodeSetConfig(
|
||||
address='deluge-{node_index}.local.svc',
|
||||
network_size=4,
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082]
|
||||
)
|
||||
|
||||
assert nodeset.nodes == [
|
||||
DelugeNodeConfig(
|
||||
address=Host(address='deluge-1.local.svc'),
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082],
|
||||
),
|
||||
DelugeNodeConfig(
|
||||
address=Host(address='deluge-2.local.svc'),
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082],
|
||||
),
|
||||
DelugeNodeConfig(
|
||||
address=Host(address='deluge-3.local.svc'),
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082],
|
||||
),
|
||||
DelugeNodeConfig(
|
||||
address=Host(address='deluge-4.local.svc'),
|
||||
daemon_port=6080,
|
||||
listen_ports=[6081, 6082],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def test_should_build_experiment_from_config():
|
||||
config_file = StringIO("""
|
||||
deluge_experiment:
|
||||
seeders: 3
|
||||
tracker_announce_url: http://localhost:2020/announce
|
||||
file_size: 1024
|
||||
shared_volume_path: /var/lib/deluge
|
||||
|
||||
nodes:
|
||||
network_size: 10
|
||||
address: 'node-{node_index}.deluge.codexbenchmarks.svc.cluster.local'
|
||||
daemon_port: 6890
|
||||
listen_ports: [ 6891, 6892 ]
|
||||
""")
|
||||
|
||||
config = DelugeExperimentConfig.model_validate(yaml.safe_load(config_file)['deluge_experiment'])
|
||||
experiment = config.build()
|
||||
|
||||
assert len(experiment.nodes) == 10
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
deluge_experiment:
|
||||
seeders: ${SEEDERS}
|
||||
tracker_announce_url: ${TRACKER_ANNOUNCE_URL}
|
||||
file_size: ${FILE_SIZE}
|
||||
repetitions: ${REPETITIONS}
|
||||
shared_volume_path: ${SHARED_VOLUME_PATH}
|
||||
|
75
poetry.lock
generated
75
poetry.lock
generated
@ -301,6 +301,68 @@ pluggy = ">=1.5,<2"
|
||||
[package.extras]
|
||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
description = "YAML parser and emitter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
|
||||
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "torrentool"
|
||||
version = "1.2.0"
|
||||
@ -315,6 +377,17 @@ files = [
|
||||
[package.extras]
|
||||
cli = ["click"]
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.12.20240917"
|
||||
description = "Typing stubs for PyYAML"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"},
|
||||
{file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
@ -329,4 +402,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "cc71833eb49981fca809cecda62c182668934f3a100ba6165a14ff895600d954"
|
||||
content-hash = "c10ab6006a3097ae8fcbac02448e98cf18f61146ab311979e1e9d5e735e2369d"
|
||||
|
@ -13,10 +13,12 @@ deluge-client = "^1.10.2"
|
||||
pathvalidate = "^3.2.1"
|
||||
torrentool = "^1.2.0"
|
||||
pydantic = "^2.10.2"
|
||||
pyyaml = "^6.0.2"
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
pytest = "^8.3.3"
|
||||
mypy = "^1.13.0"
|
||||
types-pyyaml = "^6.0.12.20240917"
|
||||
|
||||
[tool.mypy]
|
||||
ignore_missing_imports = true
|
||||
|
Loading…
x
Reference in New Issue
Block a user