mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
feat(forge): Add mount method to FileStorage & execute code in mounted workspace (#7115)
* Add `FileStorage.mount()` method, which mounts (part of) the workspace to a local path * Add `watchdog` library to watch file changes in mount * Amend `CodeExecutorComponent` * Amend `execute_python_file` to execute Python files in a workspace mount * Amend `execute_python_code` to create temporary .py file in workspace instead of as a local file * Add support for `Path` argument to `filename` parameter on `execute_python_file` * Fix `test_execute_python_code` (by making it async)
This commit is contained in:
committed by
GitHub
parent
edcbbbce25
commit
4e02f7ddb5
42
autogpt/poetry.lock
generated
42
autogpt/poetry.lock
generated
@@ -353,6 +353,7 @@ tenacity = "^8.2.2"
|
||||
tiktoken = "^0.5.0"
|
||||
toml = "^0.10.2"
|
||||
uvicorn = "^0.23.2"
|
||||
watchdog = "4.0.0"
|
||||
webdriver-manager = "^4.0.1"
|
||||
|
||||
[package.extras]
|
||||
@@ -6753,6 +6754,47 @@ files = [
|
||||
[package.dependencies]
|
||||
colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\" and python_version >= \"3.7\""}
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "4.0.0"
|
||||
description = "Filesystem events monitoring"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"},
|
||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"},
|
||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"},
|
||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"},
|
||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"},
|
||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"},
|
||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"},
|
||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"},
|
||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"},
|
||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"},
|
||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"},
|
||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"},
|
||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"},
|
||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"},
|
||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"},
|
||||
{file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"},
|
||||
{file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"},
|
||||
{file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"},
|
||||
{file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"},
|
||||
{file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"},
|
||||
{file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"},
|
||||
{file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
watchmedo = ["PyYAML (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "0.21.0"
|
||||
|
||||
@@ -84,7 +84,8 @@ def test_execute_python_file_args(
|
||||
assert result == f"{random_args_string}\n"
|
||||
|
||||
|
||||
def test_execute_python_code(
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_python_code(
|
||||
code_executor_component: CodeExecutorComponent,
|
||||
random_code: str,
|
||||
random_string: str,
|
||||
@@ -93,7 +94,7 @@ def test_execute_python_code(
|
||||
if not (is_docker_available() or we_are_running_in_a_docker_container()):
|
||||
pytest.skip("Docker is not available")
|
||||
|
||||
result: str = code_executor_component.execute_python_code(random_code)
|
||||
result: str = await code_executor_component.execute_python_code(random_code)
|
||||
assert result.replace("\r", "") == f"Hello {random_string}!\n"
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import shlex
|
||||
import string
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Iterator
|
||||
|
||||
import docker
|
||||
@@ -97,7 +98,7 @@ class CodeExecutorComponent(CommandProvider):
|
||||
),
|
||||
},
|
||||
)
|
||||
def execute_python_code(self, code: str) -> str:
|
||||
async def execute_python_code(self, code: str) -> str:
|
||||
"""
|
||||
Create and execute a Python file in a Docker container
|
||||
and return the STDOUT of the executed code.
|
||||
@@ -113,18 +114,19 @@ class CodeExecutorComponent(CommandProvider):
|
||||
str: The STDOUT captured from the code when it ran.
|
||||
"""
|
||||
|
||||
tmp_code_file = NamedTemporaryFile(
|
||||
"w", dir=self.workspace.root, suffix=".py", encoding="utf-8"
|
||||
)
|
||||
tmp_code_file.write(code)
|
||||
tmp_code_file.flush()
|
||||
temp_path = ""
|
||||
while True:
|
||||
temp_path = f"temp{self._generate_random_string()}.py"
|
||||
if not self.workspace.exists(temp_path):
|
||||
break
|
||||
await self.workspace.write_file(temp_path, code)
|
||||
|
||||
try:
|
||||
return self.execute_python_file(tmp_code_file.name)
|
||||
return self.execute_python_file(temp_path)
|
||||
except Exception as e:
|
||||
raise CommandExecutionError(*e.args)
|
||||
finally:
|
||||
tmp_code_file.close()
|
||||
self.workspace.delete_file(temp_path)
|
||||
|
||||
@command(
|
||||
["execute_python_file"],
|
||||
@@ -144,7 +146,7 @@ class CodeExecutorComponent(CommandProvider):
|
||||
),
|
||||
},
|
||||
)
|
||||
def execute_python_file(self, filename: str, args: list[str] | str = []) -> str:
|
||||
def execute_python_file(self, filename: str | Path, args: list[str] = []) -> str:
|
||||
"""Execute a Python file in a Docker container and return the output
|
||||
|
||||
Args:
|
||||
@@ -154,13 +156,7 @@ class CodeExecutorComponent(CommandProvider):
|
||||
Returns:
|
||||
str: The output of the file
|
||||
"""
|
||||
logger.info(
|
||||
f"Executing python file '{filename}' "
|
||||
f"in working directory '{self.workspace.root}'"
|
||||
)
|
||||
|
||||
if isinstance(args, str):
|
||||
args = args.split() # Convert space-separated string to a list
|
||||
logger.info(f"Executing python file '{filename}'")
|
||||
|
||||
if not str(filename).endswith(".py"):
|
||||
raise InvalidArgumentError("Invalid file type. Only .py files are allowed.")
|
||||
@@ -179,98 +175,21 @@ class CodeExecutorComponent(CommandProvider):
|
||||
"App is running in a Docker container; "
|
||||
f"executing {file_path} directly..."
|
||||
)
|
||||
result = subprocess.run(
|
||||
["python", "-B", str(file_path)] + args,
|
||||
capture_output=True,
|
||||
encoding="utf8",
|
||||
cwd=str(self.workspace.root),
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout
|
||||
else:
|
||||
raise CodeExecutionError(result.stderr)
|
||||
with self.workspace.mount() as local_path:
|
||||
result = subprocess.run(
|
||||
["python", "-B", str(file_path.relative_to(self.workspace.root))]
|
||||
+ args,
|
||||
capture_output=True,
|
||||
encoding="utf8",
|
||||
cwd=str(local_path),
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout
|
||||
else:
|
||||
raise CodeExecutionError(result.stderr)
|
||||
|
||||
logger.debug("App is not running in a Docker container")
|
||||
try:
|
||||
assert self.state.agent_id, "Need Agent ID to attach Docker container"
|
||||
|
||||
client = docker.from_env()
|
||||
image_name = "python:3-alpine"
|
||||
container_is_fresh = False
|
||||
container_name = f"{self.state.agent_id}_sandbox"
|
||||
try:
|
||||
container: DockerContainer = client.containers.get(
|
||||
container_name
|
||||
) # type: ignore
|
||||
except NotFound:
|
||||
try:
|
||||
client.images.get(image_name)
|
||||
logger.debug(f"Image '{image_name}' found locally")
|
||||
except ImageNotFound:
|
||||
logger.info(
|
||||
f"Image '{image_name}' not found locally,"
|
||||
" pulling from Docker Hub..."
|
||||
)
|
||||
# Use the low-level API to stream the pull response
|
||||
low_level_client = docker.APIClient()
|
||||
for line in low_level_client.pull(
|
||||
image_name, stream=True, decode=True
|
||||
):
|
||||
# Print the status and progress, if available
|
||||
status = line.get("status")
|
||||
progress = line.get("progress")
|
||||
if status and progress:
|
||||
logger.info(f"{status}: {progress}")
|
||||
elif status:
|
||||
logger.info(status)
|
||||
|
||||
logger.debug(f"Creating new {image_name} container...")
|
||||
container: DockerContainer = client.containers.run(
|
||||
image_name,
|
||||
["sleep", "60"], # Max 60 seconds to prevent permanent hangs
|
||||
volumes={
|
||||
str(self.workspace.root): {
|
||||
"bind": "/workspace",
|
||||
"mode": "rw",
|
||||
}
|
||||
},
|
||||
working_dir="/workspace",
|
||||
stderr=True,
|
||||
stdout=True,
|
||||
detach=True,
|
||||
name=container_name,
|
||||
) # type: ignore
|
||||
container_is_fresh = True
|
||||
|
||||
if not container.status == "running":
|
||||
container.start()
|
||||
elif not container_is_fresh:
|
||||
container.restart()
|
||||
|
||||
logger.debug(f"Running {file_path} in container {container.name}...")
|
||||
exec_result = container.exec_run(
|
||||
[
|
||||
"python",
|
||||
"-B",
|
||||
file_path.relative_to(self.workspace.root).as_posix(),
|
||||
]
|
||||
+ args,
|
||||
stderr=True,
|
||||
stdout=True,
|
||||
)
|
||||
|
||||
if exec_result.exit_code != 0:
|
||||
raise CodeExecutionError(exec_result.output.decode("utf-8"))
|
||||
|
||||
return exec_result.output.decode("utf-8")
|
||||
|
||||
except DockerException as e:
|
||||
logger.warning(
|
||||
"Could not run the script in a container. "
|
||||
"If you haven't already, please install Docker: "
|
||||
"https://docs.docker.com/get-docker/"
|
||||
)
|
||||
raise CommandExecutionError(f"Could not run the script in a container: {e}")
|
||||
return self._run_python_code_in_docker(file_path, args)
|
||||
|
||||
def validate_command(self, command_line: str, config: Config) -> tuple[bool, bool]:
|
||||
"""Check whether a command is allowed and whether it may be executed in a shell.
|
||||
@@ -395,3 +314,96 @@ class CodeExecutorComponent(CommandProvider):
|
||||
os.chdir(current_dir)
|
||||
|
||||
return f"Subprocess started with PID:'{str(process.pid)}'"
|
||||
|
||||
def _run_python_code_in_docker(self, filename: str | Path, args: list[str]) -> str:
|
||||
"""Run a Python script in a Docker container"""
|
||||
file_path = self.workspace.get_path(filename)
|
||||
try:
|
||||
assert self.state.agent_id, "Need Agent ID to attach Docker container"
|
||||
|
||||
client = docker.from_env()
|
||||
image_name = "python:3-alpine"
|
||||
container_is_fresh = False
|
||||
container_name = f"{self.state.agent_id}_sandbox"
|
||||
with self.workspace.mount() as local_path:
|
||||
try:
|
||||
container: DockerContainer = client.containers.get(
|
||||
container_name
|
||||
) # type: ignore
|
||||
except NotFound:
|
||||
try:
|
||||
client.images.get(image_name)
|
||||
logger.debug(f"Image '{image_name}' found locally")
|
||||
except ImageNotFound:
|
||||
logger.info(
|
||||
f"Image '{image_name}' not found locally,"
|
||||
" pulling from Docker Hub..."
|
||||
)
|
||||
# Use the low-level API to stream the pull response
|
||||
low_level_client = docker.APIClient()
|
||||
for line in low_level_client.pull(
|
||||
image_name, stream=True, decode=True
|
||||
):
|
||||
# Print the status and progress, if available
|
||||
status = line.get("status")
|
||||
progress = line.get("progress")
|
||||
if status and progress:
|
||||
logger.info(f"{status}: {progress}")
|
||||
elif status:
|
||||
logger.info(status)
|
||||
|
||||
logger.debug(f"Creating new {image_name} container...")
|
||||
container: DockerContainer = client.containers.run(
|
||||
image_name,
|
||||
["sleep", "60"], # Max 60 seconds to prevent permanent hangs
|
||||
volumes={
|
||||
str(local_path.resolve()): {
|
||||
"bind": "/workspace",
|
||||
"mode": "rw",
|
||||
}
|
||||
},
|
||||
working_dir="/workspace",
|
||||
stderr=True,
|
||||
stdout=True,
|
||||
detach=True,
|
||||
name=container_name,
|
||||
) # type: ignore
|
||||
container_is_fresh = True
|
||||
|
||||
if not container.status == "running":
|
||||
container.start()
|
||||
elif not container_is_fresh:
|
||||
container.restart()
|
||||
|
||||
logger.debug(f"Running {file_path} in container {container.name}...")
|
||||
|
||||
exec_result = container.exec_run(
|
||||
[
|
||||
"python",
|
||||
"-B",
|
||||
file_path.relative_to(self.workspace.root).as_posix(),
|
||||
]
|
||||
+ args,
|
||||
stderr=True,
|
||||
stdout=True,
|
||||
)
|
||||
|
||||
if exec_result.exit_code != 0:
|
||||
raise CodeExecutionError(exec_result.output.decode("utf-8"))
|
||||
|
||||
return exec_result.output.decode("utf-8")
|
||||
|
||||
except DockerException as e:
|
||||
logger.warning(
|
||||
"Could not run the script in a container. "
|
||||
"If you haven't already, please install Docker: "
|
||||
"https://docs.docker.com/get-docker/"
|
||||
)
|
||||
raise CommandExecutionError(f"Could not run the script in a container: {e}")
|
||||
|
||||
def _generate_random_string(self, length: int = 8):
|
||||
# Create a string of all letters and digits
|
||||
characters = string.ascii_letters + string.digits
|
||||
# Use random.choices to generate a random string
|
||||
random_string = "".join(random.choices(characters, k=length))
|
||||
return random_string
|
||||
|
||||
@@ -6,10 +6,16 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import contextmanager
|
||||
from io import IOBase, TextIOBase
|
||||
from pathlib import Path
|
||||
from typing import IO, Any, BinaryIO, Callable, Literal, TextIO, overload
|
||||
from typing import IO, Any, BinaryIO, Callable, Generator, Literal, TextIO, overload
|
||||
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from forge.models.config import SystemConfiguration
|
||||
|
||||
@@ -150,6 +156,32 @@ class FileStorage(ABC):
|
||||
"""
|
||||
return self._sanitize_path(relative_path)
|
||||
|
||||
@contextmanager
|
||||
def mount(self, path: str | Path = ".") -> Generator[Path, Any, None]:
|
||||
"""Mount the file storage and provide a local path."""
|
||||
local_path = tempfile.mkdtemp(dir=path)
|
||||
|
||||
observer = Observer()
|
||||
try:
|
||||
# Copy all files to the local directory
|
||||
files = self.list_files()
|
||||
for file in files:
|
||||
file_path = local_path / file
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
content = self.read_file(file, binary=True)
|
||||
file_path.write_bytes(content)
|
||||
|
||||
# Sync changes
|
||||
event_handler = FileSyncHandler(self, local_path)
|
||||
observer.schedule(event_handler, local_path, recursive=True)
|
||||
observer.start()
|
||||
|
||||
yield Path(local_path)
|
||||
finally:
|
||||
observer.stop()
|
||||
observer.join()
|
||||
shutil.rmtree(local_path)
|
||||
|
||||
def _sanitize_path(
|
||||
self,
|
||||
path: str | Path,
|
||||
@@ -202,3 +234,37 @@ class FileStorage(ABC):
|
||||
)
|
||||
|
||||
return full_path
|
||||
|
||||
|
||||
class FileSyncHandler(FileSystemEventHandler):
|
||||
def __init__(self, storage: FileStorage, path: str | Path = "."):
|
||||
self.storage = storage
|
||||
self.path = Path(path)
|
||||
|
||||
async def on_modified(self, event):
|
||||
if event.is_directory:
|
||||
return
|
||||
|
||||
file_path = Path(event.src_path).relative_to(self.path)
|
||||
content = file_path.read_bytes()
|
||||
await self.storage.write_file(file_path, content)
|
||||
|
||||
async def on_created(self, event):
|
||||
if event.is_directory:
|
||||
self.storage.make_dir(event.src_path)
|
||||
return
|
||||
|
||||
file_path = Path(event.src_path).relative_to(self.path)
|
||||
content = file_path.read_bytes()
|
||||
await self.storage.write_file(file_path, content)
|
||||
|
||||
def on_deleted(self, event):
|
||||
if event.is_directory:
|
||||
self.storage.delete_dir(event.src_path)
|
||||
return
|
||||
|
||||
file_path = event.src_path
|
||||
self.storage.delete_file(file_path)
|
||||
|
||||
def on_moved(self, event):
|
||||
self.storage.rename(event.src_path, event.dest_path)
|
||||
|
||||
@@ -6,8 +6,9 @@ from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import IO, Literal
|
||||
from typing import IO, Any, Generator, Literal
|
||||
|
||||
from .base import FileStorage, FileStorageConfiguration
|
||||
|
||||
@@ -137,3 +138,9 @@ class LocalFileStorage(FileStorage):
|
||||
restrict_to_root=self.restrict_to_root,
|
||||
)
|
||||
)
|
||||
|
||||
@contextmanager
|
||||
def mount(self, path: str | Path = ".") -> Generator[Path, Any, None]:
|
||||
"""Mount the file storage and provide a local path."""
|
||||
# No need to do anything for local storage
|
||||
yield Path(self.get_path(".")).absolute()
|
||||
|
||||
62
forge/poetry.lock
generated
62
forge/poetry.lock
generated
@@ -5648,38 +5648,40 @@ colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\" and python
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "3.0.0"
|
||||
version = "4.0.0"
|
||||
description = "Filesystem events monitoring"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"},
|
||||
{file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"},
|
||||
{file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"},
|
||||
{file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"},
|
||||
{file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"},
|
||||
{file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"},
|
||||
{file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"},
|
||||
{file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"},
|
||||
{file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"},
|
||||
{file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"},
|
||||
{file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"},
|
||||
{file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"},
|
||||
{file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"},
|
||||
{file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"},
|
||||
{file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"},
|
||||
{file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"},
|
||||
{file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"},
|
||||
{file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"},
|
||||
{file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"},
|
||||
{file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"},
|
||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"},
|
||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"},
|
||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"},
|
||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"},
|
||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"},
|
||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"},
|
||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"},
|
||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"},
|
||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"},
|
||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"},
|
||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"},
|
||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"},
|
||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"},
|
||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"},
|
||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"},
|
||||
{file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"},
|
||||
{file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"},
|
||||
{file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"},
|
||||
{file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"},
|
||||
{file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"},
|
||||
{file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"},
|
||||
{file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -6135,4 +6137,4 @@ benchmark = ["agbenchmark"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "19547e2de5ebeda0d3ef171b5dda23aecb6003c48f633ef09367095463206c3f"
|
||||
content-hash = "496083a580cb374d5bfa2223cc6e6b14d11f4f143516ae08ae1c461fb0367dac"
|
||||
|
||||
@@ -50,6 +50,7 @@ tenacity = "^8.2.2"
|
||||
tiktoken = "^0.5.0"
|
||||
toml = "^0.10.2"
|
||||
uvicorn = "^0.23.2"
|
||||
watchdog = "4.0.0"
|
||||
webdriver-manager = "^4.0.1"
|
||||
|
||||
[tool.poetry.extras]
|
||||
@@ -64,7 +65,6 @@ flake8 = "^6.0.0"
|
||||
types-requests = "^2.31.0.2"
|
||||
pytest = "^7.4.0"
|
||||
pytest-asyncio = "^0.21.1"
|
||||
watchdog = "^3.0.0"
|
||||
mock = "^5.1.0"
|
||||
autoflake = "^2.2.0"
|
||||
pydevd-pycharm = "^233.6745.319"
|
||||
|
||||
Reference in New Issue
Block a user