diff --git a/crawlab/__init__.py b/crawlab/__init__.py index e5770ba..1fb6d95 100644 --- a/crawlab/__init__.py +++ b/crawlab/__init__.py @@ -5,4 +5,4 @@ ] from crawlab.core.item import save_item, save_items -from crawlab.core.scrapy import CrawlabPipeline +from crawlab.core.scrapy.pipelines import CrawlabPipeline diff --git a/crawlab/core/item.py b/crawlab/core/item.py index 0a33cff..beb1f7c 100644 --- a/crawlab/core/item.py +++ b/crawlab/core/item.py @@ -13,5 +13,5 @@ def save_items(items: Iterable[dict]): type="data", payload=items, ) - sys.stdout.write(msg.model_dump_json()) + sys.stdout.writelines([msg.model_dump_json()]) sys.stdout.flush() diff --git a/crawlab/core/scrapy/__init__.py b/crawlab/core/scrapy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/crawlab/core/scrapy.py b/crawlab/core/scrapy/pipelines.py similarity index 100% rename from crawlab/core/scrapy.py rename to crawlab/core/scrapy/pipelines.py diff --git a/crawlab/test/__init__.py b/crawlab/test/__init__.py deleted file mode 100644 index d63b653..0000000 --- a/crawlab/test/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .action import * -from .crawlab_config import * -from .crawlab_result import * diff --git a/crawlab/test/action/__init__.py b/crawlab/test/action/__init__.py deleted file mode 100644 index 60d11b7..0000000 --- a/crawlab/test/action/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .login_test import * -from .upload_test import * diff --git a/crawlab/test/action/test_login.py b/crawlab/test/action/test_login.py deleted file mode 100644 index 3c9f969..0000000 --- a/crawlab/test/action/test_login.py +++ /dev/null @@ -1,24 +0,0 @@ -import os -import unittest -from argparse import Namespace - -from crawlab.cli.login import cli_login -from crawlab.utils.config import config -from crawlab.utils.request import get_api_address - - -class CliActionLoginTestCase(unittest.TestCase): - @staticmethod - def test_login(): - args = Namespace( - username="admin", - password="admin", - api_address=get_api_address(), - ) - cli_login(args) - assert os.path.exists(config.json_path) - assert len(config.data.get("token")) > 0 - - -if __name__ == "__main__": - unittest.main() diff --git a/crawlab/test/action/test_upload.py b/crawlab/test/action/test_upload.py deleted file mode 100644 index 6aeffe3..0000000 --- a/crawlab/test/action/test_upload.py +++ /dev/null @@ -1,139 +0,0 @@ -import os -import tempfile -import unittest -from argparse import Namespace -from datetime import datetime - -import requests - -from crawlab.cli.login import cli_login -from crawlab.cli.upload import cli_upload -from crawlab.client import get_api_address -from crawlab.utils.config import config - - -class CliActionUploadTestCase(unittest.TestCase): - endpoint = get_api_address() - - @staticmethod - def _setup(): - name = "test_spider" + f"_{int(datetime.now().timestamp())}" - dir_path = os.path.join(tempfile.gettempdir(), name) - if not os.path.exists(dir_path): - os.makedirs(dir_path) - venv_dir = os.path.join(dir_path, ".venv") - if not os.path.exists(venv_dir): - os.makedirs(venv_dir) - with open(os.path.join(venv_dir, "pyvenv.cfg"), "w") as f: - f.write("virtualenv = 20.13.0") - os.chdir(dir_path) - return name, dir_path - - def test_upload(self): - name, dir_path = self._setup() - with open(os.path.join(dir_path, "main.py"), "w") as f: - f.write("print('hello world')") - description = "test_description_" + f"_{int(datetime.now().timestamp())}" - mode = "random" - priority = 1 - cmd = "echo hello" - param = "test" - cli_login( - Namespace( - username="admin", - password="admin", - api_address=self.endpoint, - ) - ) - cli_upload( - Namespace( - id=None, - dir=None, - name=name, - description=description, - mode=mode, - priority=priority, - cmd=cmd, - param=param, - col_name=None, - create=True, - exclude_path=".venv", - ) - ) - - res = requests.get( - f"{self.endpoint}/spiders", - headers={"Authorization": config.data.get("token")}, - params={"size": 1, "page": 1, "sort": "[]"}, - ) - assert res.status_code == 200 - data = res.json().get("data") - assert len(data) == 1 - spider = data[0] - assert spider.get("name") == name - assert spider.get("description") == description - assert spider.get("mode") == mode - assert spider.get("cmd") == cmd - assert spider.get("param") == param - requests.delete( - f'{self.endpoint}/spiders/{spider.get("_id")}', - headers={"Authorization": config.data.get("token")}, - ) - - def test_upload_with_crawlab_json(self): - name, dir_path = self._setup() - description = "test_description_" + f"_{int(datetime.now().timestamp())}" - mode = "all-nodes" - priority = 1 - cmd = "echo hello" - param = "test" - with open(os.path.join(dir_path, "main.py"), "w") as f: - f.write("print('hello world')") - with open(os.path.join(dir_path, "crawlab.json"), "w") as f: - f.write( - '{"name":"%s","description":"%s","mode":"%s","priority":%d,"cmd":"%s","param":"%s"}' - % (name, description, mode, priority, cmd, param) - ) - cli_login( - Namespace( - username="admin", - password="admin", - api_address=self.endpoint, - ) - ) - cli_upload( - Namespace( - id=None, - dir=None, - name=name, - description=None, - mode=None, - priority=None, - cmd=None, - param=None, - col_name=None, - create=True, - exclude_path=None, - ) - ) - res = requests.get( - f"{self.endpoint}/spiders", - headers={"Authorization": config.data.get("token")}, - params={"size": 1, "page": 1, "sort": "[]"}, - ) - assert res.status_code == 200 - data = res.json().get("data") - spider = data[0] - assert spider.get("name") == name - assert spider.get("description") == description - assert spider.get("mode") == mode - assert spider.get("cmd") == cmd - assert spider.get("param") == param - requests.delete( - f'{self.endpoint}/spiders/{spider.get("_id")}', - headers={"Authorization": config.data.get("token")}, - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/crawlab/tests/__init__.py b/crawlab/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/crawlab/tests/action/__init__.py b/crawlab/tests/action/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/crawlab/tests/action/test_login.py b/crawlab/tests/action/test_login.py new file mode 100644 index 0000000..19a4c12 --- /dev/null +++ b/crawlab/tests/action/test_login.py @@ -0,0 +1,17 @@ +import os +from argparse import Namespace + +from crawlab.cli.login import cli_login +from crawlab.utils.config import config +from crawlab.utils.request import get_api_address + + +def test_login(): + args = Namespace( + username="admin", + password="admin", + api_address=get_api_address(), + ) + cli_login(args) + assert os.path.exists(config.json_path) + assert len(config.data.get("token")) > 0 diff --git a/crawlab/tests/action/test_upload.py b/crawlab/tests/action/test_upload.py new file mode 100644 index 0000000..5c3a278 --- /dev/null +++ b/crawlab/tests/action/test_upload.py @@ -0,0 +1,133 @@ +import os +import tempfile +from argparse import Namespace +from datetime import datetime + +import httpx +import pytest + +from crawlab.cli.login import cli_login +from crawlab.cli.upload import cli_upload +from crawlab.utils.config import config +from crawlab.utils.request import get_api_address + + +@pytest.fixture +def setup_test_dir(): + name = "test_spider" + f"_{int(datetime.now().timestamp())}" + dir_path = os.path.join(tempfile.gettempdir(), name) + if not os.path.exists(dir_path): + os.makedirs(dir_path) + venv_dir = os.path.join(dir_path, ".venv") + if not os.path.exists(venv_dir): + os.makedirs(venv_dir) + with open(os.path.join(venv_dir, "pyvenv.cfg"), "w") as f: + f.write("virtualenv = 20.13.0") + os.chdir(dir_path) + return name, dir_path + + +@pytest.fixture +def endpoint(): + return get_api_address() + + +def test_upload(setup_test_dir, endpoint): + name, dir_path = setup_test_dir + with open(os.path.join(dir_path, "main.py"), "w") as f: + f.write("print('hello world')") + description = "test_description_" + f"_{int(datetime.now().timestamp())}" + mode = "random" + priority = 1 + cmd = "echo hello" + param = "test" + + cli_login(Namespace(username="admin", password="admin", api_address=endpoint)) + cli_upload( + Namespace( + id=None, + dir=None, + name=name, + description=description, + mode=mode, + priority=priority, + cmd=cmd, + param=param, + col_name=None, + create=True, + exclude_path=".venv", + ) + ) + + res = httpx.get( + f"{endpoint}/spiders", + headers={"Authorization": config.data.get("token")}, + params={"size": 1, "page": 1, "sort": "[]"}, + ) + assert res.status_code == 200 + data = res.json().get("data") + assert len(data) == 1 + spider = data[0] + assert spider.get("name") == name + assert spider.get("description") == description + assert spider.get("mode") == mode + assert spider.get("cmd") == cmd + assert spider.get("param") == param + + httpx.delete( + f'{endpoint}/spiders/{spider.get("_id")}', + headers={"Authorization": config.data.get("token")}, + ) + + +def test_upload_with_crawlab_json(setup_test_dir, endpoint): + name, dir_path = setup_test_dir + description = "test_description_" + f"_{int(datetime.now().timestamp())}" + mode = "all-nodes" + priority = 1 + cmd = "echo hello" + param = "test" + + with open(os.path.join(dir_path, "main.py"), "w") as f: + f.write("print('hello world')") + with open(os.path.join(dir_path, "crawlab.json"), "w") as f: + f.write( + '{"name":"%s","description":"%s","mode":"%s","priority":%d,"cmd":"%s","param":"%s"}' + % (name, description, mode, priority, cmd, param) + ) + + cli_login(Namespace(username="admin", password="admin", api_address=endpoint)) + cli_upload( + Namespace( + id=None, + dir=None, + name=name, + description=None, + mode=None, + priority=None, + cmd=None, + param=None, + col_name=None, + create=True, + exclude_path=None, + ) + ) + + res = httpx.get( + f"{endpoint}/spiders", + headers={"Authorization": config.data.get("token")}, + params={"size": 1, "page": 1, "sort": "[]"}, + ) + assert res.status_code == 200 + data = res.json().get("data") + spider = data[0] + assert spider.get("name") == name + assert spider.get("description") == description + assert spider.get("mode") == mode + assert spider.get("cmd") == cmd + assert spider.get("param") == param + + httpx.delete( + f'{endpoint}/spiders/{spider.get("_id")}', + headers={"Authorization": config.data.get("token")}, + ) diff --git a/crawlab/tests/core/__init__.py b/crawlab/tests/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/crawlab/tests/core/test_item.py b/crawlab/tests/core/test_item.py new file mode 100644 index 0000000..49e057d --- /dev/null +++ b/crawlab/tests/core/test_item.py @@ -0,0 +1,85 @@ +import json + +from crawlab.core.item import save_item, save_items + + +def test_save_item_single(capsys): + test_item = {"name": "test", "value": 123} + save_item(test_item) + + capsys_res = capsys.readouterr() + assert capsys_res.err == "" + assert capsys_res.out != "" + assert capsys_res.out.startswith("{") + assert capsys_res.out.endswith("}") + message = json.loads(capsys_res.out) + + assert message["type"] == "data" + assert message["ipc"] is True + assert isinstance(message["payload"], list) + assert len(message["payload"]) == 1 + assert message["payload"][0] == test_item + + +def test_save_item_multiple(capsys): + test_items = [{"name": "test1", "value": 123}, {"name": "test2", "value": 456}] + save_item(*test_items) + + capsys_res = capsys.readouterr() + message = json.loads(capsys_res.out) + + assert message["type"] == "data" + assert message["ipc"] is True + assert isinstance(message["payload"], list) + assert len(message["payload"]) == 2 + assert message["payload"] == list(test_items) + + +def test_save_items(capsys): + test_items = [ + {"name": "test1", "value": 123}, + {"name": "test2", "value": 456}, + {"name": "test3", "value": 789}, + ] + save_items(test_items) + + capsys_res = capsys.readouterr() + message = json.loads(capsys_res.out) + + assert message["type"] == "data" + assert message["ipc"] is True + assert isinstance(message["payload"], list) + assert len(message["payload"]) == 3 + assert message["payload"] == test_items + + +def test_save_items_empty(capsys): + save_items([]) + + capsys_res = capsys.readouterr() + message = json.loads(capsys_res.out) + + assert message["type"] == "data" + assert message["ipc"] is True + assert isinstance(message["payload"], list) + assert len(message["payload"]) == 0 + + +def test_save_items_generator(capsys): + def item_generator(): + yield {"name": "test1", "value": 123} + yield {"name": "test2", "value": 456} + + save_items(item_generator()) + + capsys_res = capsys.readouterr() + message = json.loads(capsys_res.out) + + assert message["type"] == "data" + assert message["ipc"] is True + assert isinstance(message["payload"], list) + assert len(message["payload"]) == 2 + assert message["payload"] == [ + {"name": "test1", "value": 123}, + {"name": "test2", "value": 456}, + ] diff --git a/poetry.lock b/poetry.lock index 77ba559..0357697 100644 --- a/poetry.lock +++ b/poetry.lock @@ -44,6 +44,17 @@ files = [ {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -127,6 +138,17 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -162,6 +184,32 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "pydantic" version = "2.10.1" @@ -308,6 +356,28 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "rfc3986" version = "1.5.0" @@ -381,6 +451,47 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -395,4 +506,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "5066d9fa49f1201ee838de98449c0580229ccfd86d322202c1e948b22d405630" +content-hash = "378785a4d68097e416063073d3579fb90358d797033a2cb546167946a431f3c0" diff --git a/pyproject.toml b/pyproject.toml index a0728c6..fdb4948 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "crawlab-sdk" -version = "0.7.0rc3" +version = "0.7.0rc4" description = "Python SDK for Crawlab" authors = ["Marvin Zhang "] readme = "README.md" @@ -24,6 +24,7 @@ pydantic = "^2.10.1" [tool.poetry.group.dev.dependencies] ruff = "^0.3.3" +pytest = "^8.3.3" [tool.poetry.scripts] crawlab-cli = "crawlab.cli:main"