diff --git a/go/sdk/pkg/sdk/ingestion.go b/go/sdk/pkg/sdk/ingestion.go index 0f4f9bc62..58261ebbd 100644 --- a/go/sdk/pkg/sdk/ingestion.go +++ b/go/sdk/pkg/sdk/ingestion.go @@ -84,7 +84,7 @@ func (i *Ingestion) IngestFiles( if err != nil { return nil, fmt.Errorf("error marshaling chunking config: %w", err) } - writer.WriteField("chunking_settings", string(chunkingConfigJSON)) + writer.WriteField("chunking_config", string(chunkingConfigJSON)) } err := writer.Close() @@ -167,7 +167,7 @@ func (i *Ingestion) UpdateFiles( if err != nil { return nil, fmt.Errorf("error marshaling chunking config: %w", err) } - writer.WriteField("chunking_settings", string(chunkingConfigJSON)) + writer.WriteField("chunking_config", string(chunkingConfigJSON)) } err = writer.Close() diff --git a/js/sdk/src/r2rClient.ts b/js/sdk/src/r2rClient.ts index fb5c9e5d5..1fc7c7d21 100644 --- a/js/sdk/src/r2rClient.ts +++ b/js/sdk/src/r2rClient.ts @@ -412,7 +412,7 @@ export class r2rClient { metadatas?: Record[]; document_ids?: string[]; user_ids?: (string | null)[]; - chunking_settings?: Record; + chunking_config?: Record; } = {}, ): Promise { this._ensureAuthenticated(); @@ -475,8 +475,8 @@ export class r2rClient { ? JSON.stringify(options.document_ids) : undefined, user_ids: options.user_ids ? JSON.stringify(options.user_ids) : undefined, - chunking_settings: options.chunking_settings - ? JSON.stringify(options.chunking_settings) + chunking_config: options.chunking_config + ? JSON.stringify(options.chunking_config) : undefined, }; @@ -514,7 +514,7 @@ export class r2rClient { options: { document_ids: string[]; metadatas?: Record[]; - chunking_settings?: Record; + chunking_config?: Record; }, ): Promise { this._ensureAuthenticated(); @@ -552,8 +552,8 @@ export class r2rClient { metadatas: options.metadatas ? JSON.stringify(options.metadatas) : undefined, - chunking_settings: options.chunking_settings - ? JSON.stringify(options.chunking_settings) + chunking_config: options.chunking_config + ? JSON.stringify(options.chunking_config) : undefined, }; diff --git a/py/cli/commands/server.py b/py/cli/commands/server.py index 35c7a39c7..26683815a 100644 --- a/py/cli/commands/server.py +++ b/py/cli/commands/server.py @@ -180,6 +180,11 @@ def generate_report(): default=False, help="Exclude Postgres from Docker setup", ) +@click.option( + "--exclude-hatchet", + default=False, + help="Exclude Hatchet from Docker setup", +) @click.option("--project-name", default="r2r", help="Project name for Docker") @click.option("--image", help="Docker image to use") @click.option( @@ -199,6 +204,7 @@ def serve( exclude_neo4j, exclude_ollama, exclude_postgres, + exclude_hatchet, project_name, image, config_name, @@ -224,6 +230,7 @@ def serve( exclude_neo4j, exclude_ollama, exclude_postgres, + exclude_hatchet, project_name, image, config_name, diff --git a/py/cli/utils/docker_utils.py b/py/cli/utils/docker_utils.py index f15b7386f..d972bc29c 100644 --- a/py/cli/utils/docker_utils.py +++ b/py/cli/utils/docker_utils.py @@ -14,7 +14,7 @@ def bring_down_docker_compose(project_name, volumes, remove_orphans): compose_files = get_compose_files() - docker_command = f"docker compose -f {compose_files['base']} -f {compose_files['neo4j']} -f {compose_files['ollama']} -f {compose_files['postgres']}" + docker_command = f"docker compose -f {compose_files['base']} -f {compose_files['neo4j']} -f {compose_files['ollama']} -f {compose_files['postgres']} -f {compose_files['hatchet']}" docker_command += f" --project-name {project_name}" if volumes: @@ -111,6 +111,7 @@ def run_docker_serve( exclude_neo4j: bool, exclude_ollama: bool, exclude_postgres: bool, + exclude_hatchet: bool, project_name: str, image: str, config_name: Optional[str] = None, @@ -137,6 +138,7 @@ def run_docker_serve( exclude_neo4j, exclude_ollama, exclude_postgres, + exclude_hatchet, project_name, config_path, image, @@ -275,6 +277,7 @@ def set_config_env_vars(obj): else: os.environ["CONFIG_NAME"] = obj.get("config_name") or "default" + def get_compose_files(): package_dir = os.path.join( os.path.dirname(os.path.abspath(__file__)), @@ -286,6 +289,7 @@ def get_compose_files(): "neo4j": os.path.join(package_dir, "compose.neo4j.yaml"), "ollama": os.path.join(package_dir, "compose.ollama.yaml"), "postgres": os.path.join(package_dir, "compose.postgres.yaml"), + "hatchet": os.path.join(package_dir, "compose.hatchet.yaml"), } for name, path in compose_files.items(): @@ -320,6 +324,7 @@ def build_docker_command( exclude_neo4j, exclude_ollama, exclude_postgres, + exclude_hatchet, project_name, config_path, image, @@ -333,6 +338,8 @@ def build_docker_command( command += f" -f {compose_files['ollama']}" if not exclude_postgres: command += f" -f {compose_files['postgres']}" + if not exclude_hatchet: + command += f" -f {compose_files['hatchet']}" command += f" --project-name {project_name}" diff --git a/py/compose.hatchet.yaml b/py/compose.hatchet.yaml new file mode 100644 index 000000000..4c78f0c2d --- /dev/null +++ b/py/compose.hatchet.yaml @@ -0,0 +1,150 @@ +version: "3.8" + +networks: + r2r-network: + name: r2r-network + +services: + hatchet-postgres: + image: postgres:15.6 + command: postgres -c 'max_connections=200' + restart: always + hostname: "hatchet-postgres" + environment: + - POSTGRES_USER=hatchet + - POSTGRES_PASSWORD=hatchet + - POSTGRES_DB=hatchet + ports: + - "5435:5432" + volumes: + - hatchet_postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready", "-d", "hatchet"] + interval: 10s + timeout: 10s + retries: 5 + start_period: 10s + networks: + - r2r-network + + hatchet-rabbitmq: + image: "rabbitmq:3-management" + hostname: "hatchet-rabbitmq" + ports: + - "5673:5672" + - "15673:15672" + environment: + RABBITMQ_DEFAULT_USER: "user" + RABBITMQ_DEFAULT_PASS: "password" + volumes: + - "hatchet_rabbitmq_data:/var/lib/rabbitmq" + - "hatchet_rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf" + healthcheck: + test: ["CMD", "rabbitmqctl", "status"] + interval: 10s + timeout: 10s + retries: 5 + networks: + - r2r-network + + hatchet-migration: + image: ghcr.io/hatchet-dev/hatchet/hatchet-migrate:latest + environment: + DATABASE_URL: "postgres://hatchet:hatchet@hatchet-postgres:5432/hatchet" + depends_on: + hatchet-postgres: + condition: service_healthy + networks: + - r2r-network + + hatchet-setup-config: + image: ghcr.io/hatchet-dev/hatchet/hatchet-admin:latest + command: /hatchet/hatchet-admin quickstart --skip certs --generated-config-dir /hatchet/config --overwrite=false + environment: + DATABASE_URL: "postgres://hatchet:hatchet@hatchet-postgres:5432/hatchet" + DATABASE_POSTGRES_PORT: "5432" + DATABASE_POSTGRES_HOST: "hatchet-postgres" + SERVER_TASKQUEUE_RABBITMQ_URL: amqp://user:password@hatchet-rabbitmq:5672/ + SERVER_AUTH_COOKIE_DOMAIN: localhost:8080 + SERVER_AUTH_COOKIE_INSECURE: "t" + SERVER_GRPC_BIND_ADDRESS: "0.0.0.0" + SERVER_GRPC_INSECURE: "t" + SERVER_GRPC_BROADCAST_ADDRESS: localhost:7077 + SERVER_GRPC_MAX_MSG_SIZE: 104857600 + volumes: + - hatchet_certs:/hatchet/certs + - hatchet_config:/hatchet/config + depends_on: + hatchet-migration: + condition: service_completed_successfully + hatchet-rabbitmq: + condition: service_healthy + hatchet-postgres: + condition: service_healthy + networks: + - r2r-network + + hatchet-engine: + image: ghcr.io/hatchet-dev/hatchet/hatchet-engine:latest + command: /hatchet/hatchet-engine --config /hatchet/config + restart: on-failure + depends_on: + hatchet-setup-config: + condition: service_completed_successfully + hatchet-migration: + condition: service_completed_successfully + ports: + - "7077:7070" + environment: + DATABASE_URL: "postgres://hatchet:hatchet@hatchet-postgres:5432/hatchet" + SERVER_GRPC_BIND_ADDRESS: "0.0.0.0" + SERVER_GRPC_INSECURE: "t" + SERVER_GRPC_MAX_MSG_SIZE: 104857600 + volumes: + - hatchet_certs:/hatchet/certs + - hatchet_config:/hatchet/config + networks: + - r2r-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:7070/health"] + interval: 10s + timeout: 5s + retries: 5 + + hatchet-api: + image: ghcr.io/hatchet-dev/hatchet/hatchet-api:latest + command: /hatchet/hatchet-api --config /hatchet/config + restart: on-failure + depends_on: + hatchet-setup-config: + condition: service_completed_successfully + hatchet-migration: + condition: service_completed_successfully + environment: + DATABASE_URL: "postgres://hatchet:hatchet@hatchet-postgres:5432/hatchet" + volumes: + - hatchet_certs:/hatchet/certs + - hatchet_config:/hatchet/config + networks: + - r2r-network + + hatchet-frontend: + image: ghcr.io/hatchet-dev/hatchet/hatchet-frontend:latest + networks: + - r2r-network + + hatchet-caddy: + image: caddy:2.7.6-alpine + ports: + - 8081:8080 + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile + networks: + - r2r-network + +volumes: + hatchet_postgres_data: + hatchet_rabbitmq_data: + hatchet_rabbitmq.conf: + hatchet_config: + hatchet_certs: \ No newline at end of file diff --git a/py/compose.yaml b/py/compose.yaml index 56c0dd653..450bb8ba1 100644 --- a/py/compose.yaml +++ b/py/compose.yaml @@ -5,6 +5,8 @@ x-depends-on: condition: service_healthy postgres: &postgres-dependency condition: service_healthy + hatchet-engine: &hatchet-dependency + condition: service_healthy networks: r2r-network: diff --git a/py/core/base/abstractions/base.py b/py/core/base/abstractions/base.py index 56f2d5a4b..00b3fbb67 100644 --- a/py/core/base/abstractions/base.py +++ b/py/core/base/abstractions/base.py @@ -1,4 +1,31 @@ import asyncio +from typing import Any, Type, TypeVar + +from pydantic import BaseModel + +T = TypeVar("T", bound="R2RSerializable") + + +class R2RSerializable(BaseModel): + @classmethod + def from_dict(cls: Type[T], data: dict[str, Any]) -> T: + return cls(**data) + + def to_dict(self) -> dict[str, Any]: + return self.dict(exclude_unset=True) + + def to_json(self) -> str: + return self.json(exclude_unset=True) + + @classmethod + def from_json(cls: Type[T], json_str: str) -> T: + return cls.parse_raw(json_str) + + class Config: + arbitrary_types_allowed = True + json_encoders = { + bytes: lambda v: v.decode("utf-8", errors="ignore"), + } class AsyncSyncMeta(type): diff --git a/py/core/base/abstractions/document.py b/py/core/base/abstractions/document.py index 8cc07e1e6..a60ab6b43 100644 --- a/py/core/base/abstractions/document.py +++ b/py/core/base/abstractions/document.py @@ -1,5 +1,6 @@ """Abstractions for documents and their extractions.""" +import base64 import json import logging from datetime import datetime @@ -7,7 +8,9 @@ from typing import Optional, Union from uuid import NAMESPACE_DNS, UUID, uuid4 -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, validator + +from .base import R2RSerializable logger = logging.getLogger(__name__) @@ -46,41 +49,45 @@ class DocumentType(str, Enum): MP4 = "mp4" -class Document(BaseModel): +class Document(R2RSerializable): id: UUID = Field(default_factory=uuid4) group_ids: list[UUID] user_id: UUID - type: DocumentType data: Union[str, bytes] metadata: dict - def __init__(self, *args, **kwargs): - doc_type = kwargs.get("type") - if isinstance(doc_type, str): - kwargs["type"] = DocumentType(doc_type) - - # Generate UUID based on the hash of the data - if "id" not in kwargs: - data = kwargs["data"] - if isinstance(data, bytes): - data_str = data.decode("utf-8", errors="ignore") - else: - data_str = data - data_hash = uuid4(NAMESPACE_DNS, data_str) - kwargs["id"] = data_hash # Set the id based on the data hash - - super().__init__(*args, **kwargs) + @validator("data") + def validate_data(cls, v): + if isinstance(v, (str, bytes)): + return v + raise ValueError("Data must be either str or bytes") + + def dict(self, *args, **kwargs): + d = super().dict(*args, **kwargs) + if isinstance(d["data"], bytes): + d["data"] = base64.b64encode(d["data"]).decode("utf-8") + d["_is_base64"] = True + else: + d["_is_base64"] = False + return d + + @classmethod + def parse_obj(cls, obj): + if obj.get("_is_base64", False): + obj["data"] = base64.b64decode(obj["data"]) + obj.pop("_is_base64", None) + return super().parse_obj(obj) class Config: arbitrary_types_allowed = True json_encoders = { UUID: str, - bytes: lambda v: v.decode("utf-8", errors="ignore"), + bytes: lambda v: base64.b64encode(v).decode("utf-8"), } -class DocumentInfo(BaseModel): +class DocumentInfo(R2RSerializable): """Base class for document information handling.""" id: UUID diff --git a/py/core/base/abstractions/restructure.py b/py/core/base/abstractions/restructure.py index 80ab1e32e..d46cc9693 100644 --- a/py/core/base/abstractions/restructure.py +++ b/py/core/base/abstractions/restructure.py @@ -1,9 +1,10 @@ -from pydantic import BaseModel, Field +from pydantic import Field +from .base import R2RSerializable from .llm import GenerationConfig -class KGEnrichmentSettings(BaseModel): +class KGEnrichmentSettings(R2RSerializable): """Settings for knowledge graph enrichment.""" max_knowledge_triples: int = Field( diff --git a/py/core/base/api/models/auth/responses.py b/py/core/base/api/models/auth/responses.py index 3c9a1e1b5..46c6ca6b4 100644 --- a/py/core/base/api/models/auth/responses.py +++ b/py/core/base/api/models/auth/responses.py @@ -5,6 +5,7 @@ from pydantic import BaseModel from core.base.abstractions import Token +from core.base.abstractions.base import R2RSerializable from core.base.api.models.base import ResultsWrapper @@ -13,7 +14,7 @@ class TokenResponse(BaseModel): refresh_token: Token -class UserResponse(BaseModel): +class UserResponse(R2RSerializable): id: UUID email: str is_active: bool = True diff --git a/py/core/base/api/models/ingestion/responses.py b/py/core/base/api/models/ingestion/responses.py index 370785cbe..465f788ad 100644 --- a/py/core/base/api/models/ingestion/responses.py +++ b/py/core/base/api/models/ingestion/responses.py @@ -1,4 +1,4 @@ -from typing import Any, List, TypeVar +from typing import Any, TypeVar from uuid import UUID from pydantic import BaseModel, Field @@ -19,59 +19,14 @@ class FailedDocument(BaseModel): class IngestionResponse(BaseModel): - processed_documents: List[ProcessedDocument] = Field( + message: str = Field( ..., - description="List of successfully processed documents", - example=[ - { - "id": "123e4567-e89b-12d3-a456-426614174000", - "title": "Document 1", - }, - { - "id": "223e4567-e89b-12d3-a456-426614174000", - "title": "Document 2", - }, - ], + description="A message describing the result of the ingestion request.", ) - failed_documents: List[FailedDocument] = Field( + task_id: UUID = Field( ..., - description="List of documents that failed to process", - example=[ - { - "document_id": "323e4567-e89b-12d3-a456-426614174000", - "result": "Error: Invalid format", - } - ], + description="The task ID of the ingestion request.", ) - skipped_documents: List[UUID] = Field( - ..., - description="List of document IDs that were skipped during processing", - example=["423e4567-e89b-12d3-a456-426614174000"], - ) - - class Config: - json_schema_extra = { - "example": { - "processed_documents": [ - { - "id": "123e4567-e89b-12d3-a456-426614174000", - "title": "Document 1", - }, - { - "id": "223e4567-e89b-12d3-a456-426614174000", - "title": "Document 2", - }, - ], - "failed_documents": [ - { - "document_id": "323e4567-e89b-12d3-a456-426614174000", - "result": "Error: Invalid format", - } - ], - "skipped_documents": ["423e4567-e89b-12d3-a456-426614174000"], - } - } -# Create wrapped version of the response WrappedIngestionResponse = ResultsWrapper[IngestionResponse] diff --git a/py/core/base/api/models/restructure/responses.py b/py/core/base/api/models/restructure/responses.py index 292d3252f..5426bfb9d 100644 --- a/py/core/base/api/models/restructure/responses.py +++ b/py/core/base/api/models/restructure/responses.py @@ -1,4 +1,5 @@ from typing import Any, Dict +from uuid import UUID from pydantic import BaseModel @@ -6,7 +7,8 @@ class KGEnrichmentResponse(BaseModel): - enriched_content: Dict[str, Any] + message: str + task_id: UUID WrappedKGEnrichmentResponse = ResultsWrapper[KGEnrichmentResponse] diff --git a/py/core/main/__init__.py b/py/core/main/__init__.py index 4454a0ac3..3101ce659 100644 --- a/py/core/main/__init__.py +++ b/py/core/main/__init__.py @@ -4,6 +4,7 @@ # from .app_entry import r2r_app from .assembly import * +from .hatchet import * from .services import * __all__ = [ @@ -23,6 +24,8 @@ "R2RApp", ## R2R APP ENTRY # "r2r_app", + ## R2R HATCHET + "r2r_hatchet", ## R2R ASSEMBLY # Builder "R2RBuilder", diff --git a/py/core/main/api/__init__.py b/py/core/main/api/__init__.py index 573280998..4762399c2 100644 --- a/py/core/main/api/__init__.py +++ b/py/core/main/api/__init__.py @@ -1,9 +1,9 @@ -from .routes.auth.base import AuthRouter -from .routes.base_router import BaseRouter -from .routes.ingestion.base import IngestionRouter -from .routes.management.base import ManagementRouter -from .routes.restructure.base import RestructureRouter -from .routes.retrieval.base import RetrievalRouter +from .auth_router import AuthRouter +from .base_router import BaseRouter +from .ingestion_router import IngestionRouter +from .management_router import ManagementRouter +from .restructure_router import RestructureRouter +from .retrieval_router import RetrievalRouter __all__ = [ # Routes diff --git a/py/core/main/api/routes/auth/base.py b/py/core/main/api/auth_router.py similarity index 98% rename from py/core/main/api/routes/auth/base.py rename to py/core/main/api/auth_router.py index 15bf867eb..8efa092a8 100644 --- a/py/core/main/api/routes/auth/base.py +++ b/py/core/main/api/auth_router.py @@ -12,8 +12,8 @@ WrappedUserResponse, ) -from ....services.auth_service import AuthService -from ..base_router import BaseRouter, RunType +from ..services.auth_service import AuthService +from .base_router import BaseRouter, RunType oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") diff --git a/py/core/main/api/routes/base_router.py b/py/core/main/api/base_router.py similarity index 98% rename from py/core/main/api/routes/base_router.py rename to py/core/main/api/base_router.py index 42334ed0a..b626e1aca 100644 --- a/py/core/main/api/routes/base_router.py +++ b/py/core/main/api/base_router.py @@ -12,7 +12,7 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: - from ...services.base import Service + from ..services.base import Service class BaseRouter: diff --git a/py/core/main/api/routes/ingestion/ingestion_router_openapi.yml b/py/core/main/api/data/ingestion_router_openapi.yml similarity index 89% rename from py/core/main/api/routes/ingestion/ingestion_router_openapi.yml rename to py/core/main/api/data/ingestion_router_openapi.yml index 7f751ff3c..f7b8a11fd 100644 --- a/py/core/main/api/routes/ingestion/ingestion_router_openapi.yml +++ b/py/core/main/api/data/ingestion_router_openapi.yml @@ -30,7 +30,7 @@ ingest_files: metadatas: "An optional list of JSON metadata to affix to each file" document_ids: "An optional list of document ids for each file. If not provided, the system will generate a unique document id via the `generate_user_document_id` method." versions: "An optional list of versions for each file. If not provided, the system will default to the tag `v1`." - chunking_settings: "An optional dictionary to override the default chunking configuration for the ingestion process. If not provided, the system will use the default server-side chunking configuration." + chunking_config: "An optional dictionary to override the default chunking configuration for the ingestion process. If not provided, the system will use the default server-side chunking configuration." update_files: openapi_extra: @@ -58,4 +58,4 @@ update_files: files: "The list of paths of input files to update in the system." document_ids: "An optional list of document ids for each file. If not provided, the system will attempt to generate the corresponding unique from the `generate_user_document_id` method." metadatas: "An optional list of JSON metadata to affix to each file" - chunking_settings: "JSON string for chunking configuration override" + chunking_config: "JSON string for chunking configuration override" diff --git a/py/core/main/api/routes/retrieval/retrieval_router_openapi.yml b/py/core/main/api/data/retrieval_router_openapi.yml similarity index 100% rename from py/core/main/api/routes/retrieval/retrieval_router_openapi.yml rename to py/core/main/api/data/retrieval_router_openapi.yml diff --git a/py/core/main/api/routes/ingestion/base.py b/py/core/main/api/ingestion_router.py similarity index 54% rename from py/core/main/api/routes/ingestion/base.py rename to py/core/main/api/ingestion_router.py index 4ba726c29..c25f59dc3 100644 --- a/py/core/main/api/routes/ingestion/base.py +++ b/py/core/main/api/ingestion_router.py @@ -1,6 +1,7 @@ +import base64 import logging from pathlib import Path -from typing import TYPE_CHECKING, List, Optional +from typing import Optional from uuid import UUID import yaml @@ -9,25 +10,23 @@ from core.base import ChunkingConfig, R2RException from core.base.api.models.ingestion.responses import WrappedIngestionResponse -from core.base.utils import generate_user_document_id -from ....services.ingestion_service import IngestionService -from ..base_router import BaseRouter, RunType +from ...main.hatchet import r2r_hatchet +from .base_router import BaseRouter, RunType logger = logging.getLogger(__name__) class IngestionRouter(BaseRouter): - def __init__( - self, service: IngestionService, run_type: RunType = RunType.INGESTION - ): + def __init__(self, service, run_type: RunType = RunType.INGESTION): super().__init__(service, run_type) - self.service: IngestionService = service self.openapi_extras = self.load_openapi_extras() self.setup_routes() def load_openapi_extras(self): - yaml_path = Path(__file__).parent / "ingestion_router_openapi.yml" + yaml_path = ( + Path(__file__).parent / "data" / "ingestion_router_openapi.yml" + ) with open(yaml_path, "r") as yaml_file: yaml_content = yaml.safe_load(yaml_file) return yaml_content @@ -46,7 +45,7 @@ def setup_routes(self): ) @self.base_endpoint async def ingest_files_app( - files: List[UploadFile] = File( + files: list[UploadFile] = File( ..., description=ingest_files_descriptions.get("files") ), document_ids: Optional[Json[list[UUID]]] = Form( @@ -56,9 +55,9 @@ async def ingest_files_app( metadatas: Optional[Json[list[dict]]] = Form( None, description=ingest_files_descriptions.get("metadatas") ), - chunking_settings: Optional[Json[ChunkingConfig]] = Form( + chunking_config: Optional[Json[ChunkingConfig]] = Form( None, - description=ingest_files_descriptions.get("chunking_settings"), + description=ingest_files_descriptions.get("chunking_config"), ), auth_user=Depends(self.service.providers.auth.auth_wrapper), ) -> WrappedIngestionResponse: @@ -69,21 +68,7 @@ async def ingest_files_app( A valid user authentication token is required to access this endpoint, as regular users can only ingest files for their own access. More expansive group permissioning is under development. """ - from ....assembly.factory import R2RProviderFactory - - chunking_provider = None - if chunking_settings: - chunking_settings.validate() - chunking_provider = ( - R2RProviderFactory.create_chunking_provider( - chunking_settings - ) - ) - else: - logger.info( - "No chunking config override provided. Using default." - ) - + self._validate_chunking_config(chunking_config) # Check if the user is a superuser is_superuser = auth_user and auth_user.is_superuser @@ -102,27 +87,31 @@ async def ingest_files_app( # If user is not a superuser, set user_id in metadata metadata["user_id"] = str(auth_user.id) - ingestion_result = await self.service.ingest_files( - files=files, - metadatas=metadatas, - document_ids=document_ids, - user=auth_user, - chunking_provider=chunking_provider, + file_datas = await self._process_files(files) + document_ids = ( + [str(doc_id) for doc_id in document_ids] + if document_ids + else None ) - # If superuser, assign documents to groups - if is_superuser: - for idx, metadata in enumerate(metadatas or []): - if "group_ids" in metadata: - document_id = ingestion_result["processed_documents"][ - idx - ] - for group_id in metadata["group_ids"]: - await self.service.management_service.aassign_document_to_group( - document_id, group_id - ) + workflow_input = { + "file_datas": file_datas, + "document_ids": [str(doc_id) for doc_id in document_ids], + "metadatas": metadatas, + "chunking_config": ( + chunking_config.json() if chunking_config else None + ), + "user": auth_user.json(), + } + + task_id = r2r_hatchet.client.admin.run_workflow( + "ingestion-files", {"request": workflow_input} + ) - return ingestion_result + return { + "message": f"Ingestion task queued successfully.", + "task_id": str(task_id), + } update_files_extras = self.openapi_extras.get("update_files", {}) update_files_descriptions = update_files_extras.get( @@ -135,7 +124,7 @@ async def ingest_files_app( ) @self.base_endpoint async def update_files_app( - files: List[UploadFile] = File( + files: list[UploadFile] = File( ..., description=update_files_descriptions.get("files") ), document_ids: Optional[Json[list[UUID]]] = Form( @@ -145,9 +134,9 @@ async def update_files_app( metadatas: Optional[Json[list[dict]]] = Form( None, description=ingest_files_descriptions.get("metadatas") ), - chunking_settings: Optional[Json[ChunkingConfig]] = Form( + chunking_config: Optional[Json[ChunkingConfig]] = Form( None, - description=ingest_files_descriptions.get("chunking_settings"), + description=ingest_files_descriptions.get("chunking_config"), ), auth_user=Depends(self.service.providers.auth.auth_wrapper), ) -> WrappedIngestionResponse: @@ -161,19 +150,68 @@ async def update_files_app( A valid user authentication token is required to access this endpoint, as regular users can only update their own files. More expansive group permissioning is under development. """ - from ....assembly.factory import R2RProviderFactory - - chunking_provider = None - if chunking_settings: - config = ChunkingConfig(**chunking_settings) - chunking_provider = ( - R2RProviderFactory.create_chunking_provider(config) - ) - - return await self.service.update_files( - files=files, - metadatas=metadatas, - document_ids=document_ids, - chunking_provider=chunking_provider, - user=auth_user, + + self._validate_chunking_config(chunking_config) + # Check if the user is a superuser + is_superuser = auth_user and auth_user.is_superuser + + # Handle user management logic at the request level + if not is_superuser: + for metadata in metadatas or []: + if "user_id" in metadata and metadata["user_id"] != str( + auth_user.id + ): + raise R2RException( + status_code=403, + message="Non-superusers cannot set user_id in metadata.", + ) + + # Set user_id in metadata for non-superusers + metadata["user_id"] = str(auth_user.id) + + file_datas = await self._process_files(files) + + workflow_input = { + "file_datas": file_datas, + "document_ids": [str(doc_id) for doc_id in document_ids], + "metadatas": metadatas, + "chunking_config": ( + chunking_config.json() if chunking_config else None + ), + "user": auth_user.json(), + } + + task_id = r2r_hatchet.client.admin.run_workflow( + "update-files", {"request": workflow_input} + ) + + return { + "message": f"Update task queued successfully.", + "task_id": str(task_id), + } + + @staticmethod + def _validate_chunking_config(chunking_config): + from ..assembly.factory import R2RProviderFactory + + if chunking_config: + chunking_config.validate() + R2RProviderFactory.create_chunking_provider(chunking_config) + else: + logger.info("No chunking config override provided. Using default.") + + @staticmethod + async def _process_files(files): + import base64 + + file_datas = [] + for file in files: + content = await file.read() + file_datas.append( + { + "filename": file.filename, + "content": base64.b64encode(content).decode("utf-8"), + "content_type": file.content_type, + } ) + return file_datas diff --git a/py/core/main/api/routes/management/base.py b/py/core/main/api/management_router.py similarity index 99% rename from py/core/main/api/routes/management/base.py rename to py/core/main/api/management_router.py index 222411d1f..e4bb1f0ca 100644 --- a/py/core/main/api/routes/management/base.py +++ b/py/core/main/api/management_router.py @@ -27,8 +27,8 @@ ) from core.base.logging import AnalysisTypes, LogFilterCriteria -from ....services.management_service import ManagementService -from ..base_router import BaseRouter, RunType +from ..services.management_service import ManagementService +from .base_router import BaseRouter, RunType class ManagementRouter(BaseRouter): diff --git a/py/core/main/api/routes/restructure/base.py b/py/core/main/api/restructure_router.py similarity index 54% rename from py/core/main/api/routes/restructure/base.py rename to py/core/main/api/restructure_router.py index 22c2727ae..3c8d96ea7 100644 --- a/py/core/main/api/routes/restructure/base.py +++ b/py/core/main/api/restructure_router.py @@ -3,9 +3,13 @@ from fastapi import Body, Depends from core.base import KGEnrichmentSettings -from core.main.api.routes.base_router import BaseRouter, RunType +from core.base.api.models.restructure.responses import ( + WrappedKGEnrichmentResponse, +) -from ....services.restructure_service import RestructureService +from ...main.hatchet import r2r_hatchet +from ..services.restructure_service import RestructureService +from .base_router import BaseRouter, RunType class RestructureRouter(BaseRouter): @@ -22,16 +26,28 @@ def setup_routes(self): @self.router.post("/enrich_graph") @self.base_endpoint async def enrich_graph( - KGEnrichmentSettings: Union[dict, KGEnrichmentSettings] = Body( + kg_enrichment_settings: KGEnrichmentSettings = Body( ..., description="Settings for knowledge graph enrichment", ), auth_user=(Depends(self.service.providers.auth.auth_wrapper)), - ): + ) -> WrappedKGEnrichmentResponse: """ Perform graph enrichment, e.g. GraphRAG, over the ingested documents. Returns: Dict[str, Any]: Results of the graph enrichment process. """ - return await self.service.enrich_graph() + workflow_input = { + "kg_enrichment_settings": kg_enrichment_settings.json(), + "user": auth_user.json(), + } + + task_id = r2r_hatchet.client.admin.run_workflow( + "enrich-graph", {"request": workflow_input} + ) + + return { + "message": "Graph enrichment task queued successfully.", + "task_id": str(task_id), + } diff --git a/py/core/main/api/routes/retrieval/base.py b/py/core/main/api/retrieval_router.py similarity index 97% rename from py/core/main/api/routes/retrieval/base.py rename to py/core/main/api/retrieval_router.py index 3e71bde3f..3dacdbf20 100644 --- a/py/core/main/api/routes/retrieval/base.py +++ b/py/core/main/api/retrieval_router.py @@ -19,8 +19,8 @@ WrappedSearchResponse, ) -from ....services.retrieval_service import RetrievalService -from ..base_router import BaseRouter +from ..services.retrieval_service import RetrievalService +from .base_router import BaseRouter class RetrievalRouter(BaseRouter): @@ -33,7 +33,9 @@ def __init__( self.setup_routes() def load_openapi_extras(self): - yaml_path = Path(__file__).parent / "retrieval_router_openapi.yml" + yaml_path = ( + Path(__file__).parent / "data" / "retrieval_router_openapi.yml" + ) with open(yaml_path, "r") as yaml_file: yaml_content = yaml.safe_load(yaml_file) return yaml_content diff --git a/py/core/main/api/routes/__init__.py b/py/core/main/api/routes/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/py/core/main/api/routes/auth/__init__.py b/py/core/main/api/routes/auth/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/py/core/main/api/routes/ingestion/__init__.py b/py/core/main/api/routes/ingestion/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/py/core/main/api/routes/management/__init__.py b/py/core/main/api/routes/management/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/py/core/main/api/routes/restructure/__init__.py b/py/core/main/api/routes/restructure/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/py/core/main/api/routes/retrieval/__init__.py b/py/core/main/api/routes/retrieval/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/py/core/main/app.py b/py/core/main/app.py index ba659d3bc..ef7682ad6 100644 --- a/py/core/main/app.py +++ b/py/core/main/app.py @@ -2,24 +2,37 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.openapi.utils import get_openapi -from .api.routes.auth.base import AuthRouter -from .api.routes.ingestion.base import IngestionRouter -from .api.routes.management.base import ManagementRouter -from .api.routes.restructure.base import RestructureRouter -from .api.routes.retrieval.base import RetrievalRouter +from .api.auth_router import AuthRouter +from .api.ingestion_router import IngestionRouter +from .api.management_router import ManagementRouter +from .api.restructure_router import RestructureRouter +from .api.retrieval_router import RetrievalRouter from .config import R2RConfig +from .hatchet import ( + EnrichGraphWorkflow, + IngestFilesWorkflow, + UpdateFilesWorkflow, + r2r_hatchet, +) +from .services.ingestion_service import IngestionService +from .services.restructure_service import RestructureService + class R2RApp: def __init__( self, config: R2RConfig, auth_router: AuthRouter, + ingestion_service: IngestionService, + restructure_service: RestructureService, ingestion_router: IngestionRouter, management_router: ManagementRouter, retrieval_router: RestructureRouter, restructure_router: RetrievalRouter, ): self.config = config + self.ingestion_service = ingestion_service + self.restructure_service = restructure_service self.ingestion_router = ingestion_router self.management_router = management_router self.retrieval_router = retrieval_router @@ -27,13 +40,9 @@ def __init__( self.restructure_router = restructure_router self.app = FastAPI() self._setup_routes() + self._setup_hatchet_worker() self._apply_cors() - def serve(self, host: str = "0.0.0.0", port: int = 8000): - import uvicorn - - uvicorn.run(self.app, host=host, port=port) - def _setup_routes(self): # Include routers in the app @@ -51,6 +60,22 @@ async def openapi_spec(): routes=self.app.routes, ) + def _setup_hatchet_worker( + self, + ): + self.r2r_worker = r2r_hatchet.worker("r2r-worker") + + self.r2r_worker.register_workflow( + IngestFilesWorkflow(self.ingestion_service) + ) + self.r2r_worker.register_workflow( + UpdateFilesWorkflow(self.ingestion_service) + ) + + self.r2r_worker.register_workflow( + EnrichGraphWorkflow(self.restructure_service) + ) + def _apply_cors(self): origins = ["*", "http://localhost:3000", "http://localhost:8000"] self.app.add_middleware( @@ -60,3 +85,19 @@ def _apply_cors(self): allow_methods=["*"], allow_headers=["*"], ) + + def serve( + self, host: str = "0.0.0.0", port: int = 8000, max_threads: int = 1 + ): + # Start the Hatchet worker in a separate thread + import threading + + import uvicorn + + r2r_worker_thread = threading.Thread( + target=self.r2r_worker.start, daemon=True + ) + r2r_worker_thread.start() + + # Run the FastAPI app + uvicorn.run(self.app, host=host, port=port) diff --git a/py/core/main/app_entry.py b/py/core/main/app_entry.py index a94329c95..30cef82f7 100644 --- a/py/core/main/app_entry.py +++ b/py/core/main/app_entry.py @@ -1,20 +1,24 @@ import logging import os -from enum import Enum +import threading from typing import Optional from fastapi import FastAPI +from hatchet.base import worker from .assembly import R2RBuilder, R2RConfig logger = logging.getLogger(__name__) +def start_hatchet_worker(): + worker.start() + + def r2r_app( config_name: Optional[str] = "default", config_path: Optional[str] = None, ) -> FastAPI: - config = R2RConfig.load(config_name, config_path) if ( @@ -25,8 +29,14 @@ def r2r_app( "Must set OPENAI_API_KEY in order to initialize OpenAIEmbeddingProvider." ) - # Return the FastAPI app - return R2RBuilder(config=config).build().app + # Build the FastAPI app + app = R2RBuilder(config=config).build().app + + # Start the Hatchet worker in a separate thread + worker_thread = threading.Thread(target=start_hatchet_worker, daemon=True) + worker_thread.start() + + return app logging.basicConfig(level=logging.INFO) diff --git a/py/core/main/assembly/builder.py b/py/core/main/assembly/builder.py index 54b709186..95daef29c 100644 --- a/py/core/main/assembly/builder.py +++ b/py/core/main/assembly/builder.py @@ -21,11 +21,11 @@ SearchPipeline, ) -from ..api.routes.auth.base import AuthRouter -from ..api.routes.ingestion.base import IngestionRouter -from ..api.routes.management.base import ManagementRouter -from ..api.routes.restructure.base import RestructureRouter -from ..api.routes.retrieval.base import RetrievalRouter +from ..api.auth_router import AuthRouter +from ..api.ingestion_router import IngestionRouter +from ..api.management_router import ManagementRouter +from ..api.restructure_router import RestructureRouter +from ..api.retrieval_router import RetrievalRouter from ..app import R2RApp from ..config import R2RConfig from ..services.auth_service import AuthService @@ -340,6 +340,8 @@ def build(self, *args, **kwargs) -> R2RApp: return R2RApp( config=self.config, auth_router=auth_router, + ingestion_service=ingestion_service, + restructure_service=restructure_service, ingestion_router=ingestion_router, management_router=management_router, retrieval_router=retrieval_router, diff --git a/py/core/main/assembly/factory.py b/py/core/main/assembly/factory.py index 0578cf91e..484a412a8 100644 --- a/py/core/main/assembly/factory.py +++ b/py/core/main/assembly/factory.py @@ -246,7 +246,7 @@ def create_providers( auth_provider_override: Optional[AuthProvider] = None, database_provider_override: Optional[DatabaseProvider] = None, parsing_provider_override: Optional[ParsingProvider] = None, - chunking_settings: Optional[ChunkingProvider] = None, + chunking_config: Optional[ChunkingProvider] = None, *args, **kwargs, ) -> R2RProviders: @@ -291,7 +291,7 @@ def create_providers( self.config.parsing, *args, **kwargs ) ) - chunking_provider = chunking_settings or self.create_chunking_provider( + chunking_provider = chunking_config or self.create_chunking_provider( self.config.chunking, *args, **kwargs ) diff --git a/py/core/main/config.py b/py/core/main/config.py index b2f26e6a5..2cc8684bb 100644 --- a/py/core/main/config.py +++ b/py/core/main/config.py @@ -126,9 +126,9 @@ def __init__( self.embedding = EmbeddingConfig.create(**self.embedding) self.kg = KGConfig.create(**self.kg) self.logging = LoggingConfig.create(**self.logging) - self.parsing = ParsingConfig.create( - chunking_config=self.chunking, **self.parsing - ) + if not "chunking_config" in self.parsing: + self.parsing["chunking_config"] = self.chunking + self.parsing = ParsingConfig.create(**self.parsing) self.prompt = PromptConfig.create(**self.prompt) self.agent = AgentConfig.create(**self.agent) diff --git a/py/core/main/hatchet/__init__.py b/py/core/main/hatchet/__init__.py new file mode 100644 index 000000000..a0911feff --- /dev/null +++ b/py/core/main/hatchet/__init__.py @@ -0,0 +1,10 @@ +from .base import r2r_hatchet +from .ingestion_workflow import IngestFilesWorkflow, UpdateFilesWorkflow +from .restructure_workflow import EnrichGraphWorkflow + +__all__ = [ + "r2r_hatchet", + "IngestFilesWorkflow", + "UpdateFilesWorkflow", + "EnrichGraphWorkflow", +] diff --git a/py/core/main/hatchet/base.py b/py/core/main/hatchet/base.py new file mode 100644 index 000000000..e186aebec --- /dev/null +++ b/py/core/main/hatchet/base.py @@ -0,0 +1,3 @@ +from hatchet_sdk import Hatchet + +r2r_hatchet = Hatchet() diff --git a/py/core/main/hatchet/ingestion_workflow.py b/py/core/main/hatchet/ingestion_workflow.py new file mode 100644 index 000000000..b30b420a7 --- /dev/null +++ b/py/core/main/hatchet/ingestion_workflow.py @@ -0,0 +1,36 @@ +from hatchet_sdk import Context + +from ..services import IngestionService, IngestionServiceAdapter +from .base import r2r_hatchet + + +@r2r_hatchet.workflow( + name="ingestion-files", on_events=["file:ingest"], timeout=3600 +) +class IngestFilesWorkflow: + def __init__(self, ingestion_service: IngestionService): + self.ingestion_service = ingestion_service + + @r2r_hatchet.step(retries=3) + async def ingest_files(self, context: Context) -> None: + data = context.workflow_input()["request"] + + parsed_data = IngestionServiceAdapter.parse_ingest_files_input(data) + + await self.ingestion_service.ingest_files(**parsed_data) + + +@r2r_hatchet.workflow( + name="update-files", on_events=["file:update"], timeout=3600 +) +class UpdateFilesWorkflow: + def __init__(self, ingestion_service: IngestionService): + self.ingestion_service = ingestion_service + + @r2r_hatchet.step(retries=3) + async def update_files(self, context: Context) -> None: + data = context.workflow_input()["request"] + + parsed_data = IngestionServiceAdapter.parse_update_files_input(data) + + await self.ingestion_service.update_files(**parsed_data) diff --git a/py/core/main/hatchet/restructure_workflow.py b/py/core/main/hatchet/restructure_workflow.py new file mode 100644 index 000000000..99e2ecae1 --- /dev/null +++ b/py/core/main/hatchet/restructure_workflow.py @@ -0,0 +1,20 @@ +from hatchet_sdk import Context + +from ..services import RestructureService, RestructureServiceAdapter +from .base import r2r_hatchet + + +@r2r_hatchet.workflow( + name="enrich-graph", on_events=["graph:enrich"], timeout=3600 +) +class EnrichGraphWorkflow: + def __init__(self, restructure_service: RestructureService): + self.restructure_service = restructure_service + + @r2r_hatchet.step(retries=0) + async def enrich_graph(self, context: Context) -> None: + data = context.workflow_input()["request"] + + parsed_data = RestructureServiceAdapter.parse_enrich_graph_input(data) + + await self.restructure_service.enrich_graph(**parsed_data) diff --git a/py/core/main/services/__init__.py b/py/core/main/services/__init__.py index 8fb32f0fb..780d47633 100644 --- a/py/core/main/services/__init__.py +++ b/py/core/main/services/__init__.py @@ -1,13 +1,15 @@ from .auth_service import AuthService -from .ingestion_service import IngestionService +from .ingestion_service import IngestionService, IngestionServiceAdapter from .management_service import ManagementService -from .restructure_service import RestructureService +from .restructure_service import RestructureService, RestructureServiceAdapter from .retrieval_service import RetrievalService __all__ = [ "AuthService", "IngestionService", + "IngestionServiceAdapter", "ManagementService", - "RetrievalService", + "RestructureServiceAdapter", "RestructureService", + "RetrievalService", ] diff --git a/py/core/main/services/ingestion_service.py b/py/core/main/services/ingestion_service.py index b4785ff74..3a8c23671 100644 --- a/py/core/main/services/ingestion_service.py +++ b/py/core/main/services/ingestion_service.py @@ -1,3 +1,5 @@ +import base64 +import json import logging from collections import defaultdict from datetime import datetime @@ -19,7 +21,7 @@ to_async_generator, ) from core.base.api.models import IngestionResponse -from core.base.providers import ChunkingProvider +from core.base.providers import ChunkingConfig, ChunkingProvider from core.telemetry.telemetry_decorator import telemetry_event from ...base.api.models.auth.responses import UserResponse @@ -56,167 +58,215 @@ def __init__( @telemetry_event("IngestFiles") async def ingest_files( self, - files: list[UploadFile], + file_datas: list[dict], user: UserResponse, metadatas: Optional[list[dict]] = None, document_ids: Optional[list[UUID]] = None, - chunking_provider: Optional[ChunkingProvider] = None, + chunking_config: Optional[ChunkingConfig] = None, *args: Any, **kwargs: Any, ) -> IngestionResponse: - if not files: + if not file_datas: raise R2RException( status_code=400, message="No files provided for ingestion." ) - if len(files) > MAX_FILES_PER_INGESTION: + if len(file_datas) > MAX_FILES_PER_INGESTION: raise R2RException( status_code=400, message=f"Exceeded maximum number of files per ingestion: {MAX_FILES_PER_INGESTION}.", ) - try: - documents = [] - for iteration, file in enumerate(files): - if not file.filename: - raise R2RException( - status_code=400, message="File name not provided." - ) - - document_metadata = metadatas[iteration] if metadatas else {} + from ..assembly.factory import R2RProviderFactory - # document id is dynamically generated from the filename and user id, unless explicitly provided - document_id = ( - document_ids[iteration] - if document_ids - else generate_user_document_id(file.filename, user.id) + documents = [] + for iteration, file_data in enumerate(file_datas): + if not file_data.get("filename"): + raise R2RException( + status_code=400, message="File name not provided." ) - document = self._file_to_document( - file, user, document_id, document_metadata + + chunking_provider = None + if chunking_config: + chunking_config.validate() + # Validate the chunking settings + chunking_provider = ( + R2RProviderFactory.create_chunking_provider( + chunking_config + ) ) - documents.append(document) - # ingests all documents in parallel - return await self.ingest_documents( - documents, - chunking_provider=chunking_provider, - *args, - **kwargs, + document_metadata = metadatas[iteration] if metadatas else {} + + # document id is dynamically generated from the filename and user id, unless explicitly provided + document_id = ( + document_ids[iteration] + if document_ids + else generate_user_document_id(file_data["filename"], user.id) + ) + document = self._file_data_to_document( + file_data, user, document_id, document_metadata + ) + documents.append(document) + + # ingests all documents in parallel + return await self.ingest_documents( + documents, + chunking_provider=chunking_provider, + *args, + **kwargs, + ) + + def _file_data_to_document( + self, + file_info: dict, + user: UserResponse, + document_id: UUID, + metadata: dict, + ) -> Document: + file_extension = file_info["filename"].split(".")[-1].lower() + if file_extension.upper() not in DocumentType.__members__: + raise R2RException( + status_code=415, + message=f"'{file_extension}' is not a valid DocumentType.", ) - finally: - for file in files: - file.file.close() + document_title = ( + metadata.get("title") or file_info["filename"].split("/")[-1] + ) + metadata["title"] = document_title + + # Decode the base64 encoded content + content = base64.b64decode(file_info["content"]) + + return Document( + id=document_id, + group_ids=metadata.get("group_ids", []), + user_id=user.id, + type=DocumentType[file_extension.upper()], + data=content, + metadata=metadata, + ) @telemetry_event("UpdateFiles") async def update_files( self, - files: list[UploadFile], + file_datas: list[dict], user: UserResponse, - document_ids: Optional[list[UUID]], + document_ids: list[UUID], metadatas: Optional[list[dict]] = None, - chunking_provider: Optional[ChunkingProvider] = None, + chunking_config: Optional[ChunkingConfig] = None, *args: Any, **kwargs: Any, ) -> IngestionResponse: - if not files: + if not file_datas: raise R2RException( status_code=400, message="No files provided for update." ) - if not self.providers.database: + if len(file_datas) != len(document_ids): raise R2RException( - status_code=501, - message="Database provider is not available for updating documents.", + status_code=400, + message="Number of files does not match number of document IDs.", ) - try: - if document_ids: - if len(document_ids) != len(files): - raise R2RException( - status_code=400, - message="Number of ids does not match number of files.", - ) - else: - document_ids = [ - generate_user_document_id(file.filename, user.id) - for file in files - ] - if len(files) > MAX_FILES_PER_INGESTION: + if len(file_datas) > MAX_FILES_PER_INGESTION: + raise R2RException( + status_code=400, + message=f"Exceeded maximum number of files per update: {MAX_FILES_PER_INGESTION}.", + ) + from ..assembly.factory import R2RProviderFactory + + chunking_provider = None + if chunking_config: + chunking_config.validate() + # Validate the chunking settings + chunking_provider = R2RProviderFactory.create_chunking_provider( + chunking_config + ) + + if document_ids: + if len(document_ids) != len(file_datas): raise R2RException( status_code=400, - message=f"Exceeded maximum number of files per ingestion: {MAX_FILES_PER_INGESTION}.", - ) - - documents_overview = [] - - offset = 0 - while True: - documents_overview_page = ( - self.providers.database.relational.get_documents_overview( - filter_document_ids=document_ids, - filter_user_ids=( - [user.id] if not user.is_superuser else None - ), - offset=offset, - limit=OVERVIEW_FETCH_PAGE_SIZE, - ) + message="Number of ids does not match number of files.", ) - documents_overview.extend(documents_overview_page) - if len(documents_overview_page) < OVERVIEW_FETCH_PAGE_SIZE: - break - offset += 1 - - documents = [] - new_versions = [] - - for it, (file, doc_id, doc_info) in enumerate( - zip(files, document_ids, documents_overview) - ): - if not doc_info: - raise R2RException( - status_code=404, - message=f"Document with id {doc_id} not found.", - ) + else: + document_ids = [ + generate_user_document_id(file["filename"], user.id) + for file in file_datas + ] + if len(file_datas) > MAX_FILES_PER_INGESTION: + raise R2RException( + status_code=400, + message=f"Exceeded maximum number of files per ingestion: {MAX_FILES_PER_INGESTION}.", + ) - new_version = increment_version(doc_info.version) - new_versions.append(new_version) + documents_overview = [] - updated_metadata = ( - metadatas[it] if metadatas else doc_info.metadata + offset = 0 + while True: + documents_overview_page = ( + self.providers.database.relational.get_documents_overview( + filter_document_ids=document_ids, + filter_user_ids=( + [user.id] if not user.is_superuser else None + ), + offset=offset, + limit=OVERVIEW_FETCH_PAGE_SIZE, ) - updated_metadata["title"] = ( - updated_metadata.get("title", None) - or file.filename.split("/")[-1] + ) + documents_overview.extend(documents_overview_page) + if len(documents_overview_page) < OVERVIEW_FETCH_PAGE_SIZE: + break + offset += 1 + + documents = [] + new_versions = [] + + for it, (file_data, document_id, doc_info) in enumerate( + zip(file_datas, document_ids, documents_overview) + ): + if not doc_info: + raise R2RException( + status_code=404, + message=f"Document with id {document_id} not found.", ) - document = self._file_to_document( - file, user, doc_id, updated_metadata - ) - documents.append(document) - - ingestion_results = await self.ingest_documents( - documents, - chunking_provider=chunking_provider, - versions=new_versions, - *args, - **kwargs, + new_version = increment_version(doc_info.version) + new_versions.append(new_version) + + updated_metadata = ( + metadatas[it] if metadatas else doc_info.metadata + ) + updated_metadata["title"] = ( + updated_metadata.get("title", None) + or file_data["filename"].split("/")[-1] ) - for doc_id, old_version in zip( - document_ids, - [doc_info.version for doc_info in documents_overview], - ): - self.providers.database.vector.delete( - filters={ - "document_id": {"$eq": doc_id}, - "version": {"$eq": old_version}, - } - ) - self.providers.database.relational.delete_from_documents_overview( - doc_id, old_version - ) + document = self._file_data_to_document( + file_data, user, document_id, updated_metadata + ) + documents.append(document) + + ingestion_results = await self.ingest_documents( + documents, + chunking_provider=chunking_provider, + versions=new_versions, + *args, + **kwargs, + ) - return ingestion_results + for doc_id, old_version in zip( + document_ids, + [doc_info.version for doc_info in documents_overview], + ): + self.providers.database.vector.delete( + filters={ + "document_id": {"$eq": doc_id}, + "version": {"$eq": old_version}, + } + ) + self.providers.database.relational.delete_from_documents_overview( + doc_id, old_version + ) - finally: - for file in files: - file.file.close() + return ingestion_results async def ingest_documents( self, @@ -447,3 +497,86 @@ async def _process_ingestion_results( ], "skipped_documents": skipped_ids, } + + +class IngestionServiceAdapter: + @staticmethod + def _parse_user_data(user_data): + if isinstance(user_data, str): + try: + user_data = json.loads(user_data) + except json.JSONDecodeError: + raise ValueError(f"Invalid user data format: {user_data}") + return UserResponse.from_dict(user_data) + + @staticmethod + def prepare_ingest_files_input( + file_datas: list[dict], + user: UserResponse, + metadatas: Optional[list[dict]] = None, + document_ids: Optional[list[UUID]] = None, + chunking_config: Optional[ChunkingConfig] = None, + ) -> dict: + return { + "file_datas": file_datas, + "user": user.to_dict(), + "metadatas": metadatas, + "document_ids": ( + [str(doc_id) for doc_id in document_ids] + if document_ids + else None + ), + "chunking_config": ( + chunking_config.to_dict() if chunking_config else None + ), + } + + @staticmethod + def parse_ingest_files_input(data: dict): + return { + "file_datas": data["file_datas"], + "user": IngestionServiceAdapter._parse_user_data(data["user"]), + "metadatas": data["metadatas"], + "document_ids": ( + [UUID(doc_id) for doc_id in data["document_ids"]] + if data["document_ids"] + else None + ), + "chunking_config": ( + ChunkingConfig.from_dict(data["chunking_config"]) + if data["chunking_config"] + else None + ), + } + + @staticmethod + def prepare_update_files_input( + file_datas: list[dict], + user: UserResponse, + document_ids: list[UUID], + metadatas: Optional[list[dict]] = None, + chunking_config: Optional[ChunkingConfig] = None, + ) -> dict: + return { + "file_datas": file_datas, + "user": user.to_dict(), + "document_ids": [str(doc_id) for doc_id in document_ids], + "metadatas": metadatas, + "chunking_config": ( + chunking_config.to_dict() if chunking_config else None + ), + } + + @staticmethod + def parse_update_files_input(data: dict): + return { + "file_datas": data["file_datas"], + "user": IngestionServiceAdapter._parse_user_data(data["user"]), + "document_ids": [UUID(doc_id) for doc_id in data["document_ids"]], + "metadatas": data["metadatas"], + "chunking_config": ( + ChunkingConfig.from_dict(data["chunking_config"]) + if data["chunking_config"] + else None + ), + } diff --git a/py/core/main/services/restructure_service.py b/py/core/main/services/restructure_service.py index 834328033..8031ff55e 100644 --- a/py/core/main/services/restructure_service.py +++ b/py/core/main/services/restructure_service.py @@ -1,8 +1,10 @@ +import json import logging from typing import Any, Dict, Optional, Union from core.base import R2RException, RunLoggingSingleton, RunManager from core.base.abstractions import KGEnrichmentSettings +from core.base.api.models.auth.responses import UserResponse from ..abstractions import R2RAgents, R2RPipelines, R2RProviders from ..config import R2RConfig @@ -65,3 +67,13 @@ async def input_generator(): raise R2RException( status_code=500, message=f"Graph enrichment failed: {str(e)}" ) + + +class RestructureServiceAdapter: + @staticmethod + def parse_enrich_graph_input(data: dict): + return { + "kg_enrichment_settings": KGEnrichmentSettings.from_dict( + json.loads(data["kg_enrichment_settings"]) + ) + } diff --git a/py/core/main/services/retrieval_service.py b/py/core/main/services/retrieval_service.py index dc3e17db8..237254dac 100644 --- a/py/core/main/services/retrieval_service.py +++ b/py/core/main/services/retrieval_service.py @@ -1,3 +1,4 @@ +import json import logging import time from datetime import datetime @@ -315,3 +316,116 @@ async def stream_response(): raise R2RException( status_code=500, message="Internal Server Error" ) + + +class RetrievalServiceAdapter: + @staticmethod + def _parse_user_data(user_data): + if isinstance(user_data, str): + try: + user_data = json.loads(user_data) + except json.JSONDecodeError: + raise ValueError(f"Invalid user data format: {user_data}") + return UserResponse.from_dict(user_data) + + @staticmethod + def prepare_search_input( + query: str, + vector_search_settings: VectorSearchSettings, + kg_search_settings: KGSearchSettings, + user: UserResponse, + ) -> dict: + return { + "query": query, + "vector_search_settings": vector_search_settings.to_dict(), + "kg_search_settings": kg_search_settings.to_dict(), + "user": user.to_dict(), + } + + @staticmethod + def parse_search_input(data: dict): + return { + "query": data["query"], + "vector_search_settings": VectorSearchSettings.from_dict( + data["vector_search_settings"] + ), + "kg_search_settings": KGSearchSettings.from_dict( + data["kg_search_settings"] + ), + "user": RetrievalServiceAdapter._parse_user_data(data["user"]), + } + + @staticmethod + def prepare_rag_input( + query: str, + vector_search_settings: VectorSearchSettings, + kg_search_settings: KGSearchSettings, + rag_generation_config: GenerationConfig, + task_prompt_override: Optional[str], + user: UserResponse, + ) -> dict: + return { + "query": query, + "vector_search_settings": vector_search_settings.to_dict(), + "kg_search_settings": kg_search_settings.to_dict(), + "rag_generation_config": rag_generation_config.to_dict(), + "task_prompt_override": task_prompt_override, + "user": user.to_dict(), + } + + @staticmethod + def parse_rag_input(data: dict): + return { + "query": data["query"], + "vector_search_settings": VectorSearchSettings.from_dict( + data["vector_search_settings"] + ), + "kg_search_settings": KGSearchSettings.from_dict( + data["kg_search_settings"] + ), + "rag_generation_config": GenerationConfig.from_dict( + data["rag_generation_config"] + ), + "task_prompt_override": data["task_prompt_override"], + "user": RetrievalServiceAdapter._parse_user_data(data["user"]), + } + + @staticmethod + def prepare_agent_input( + messages: list[Message], + vector_search_settings: VectorSearchSettings, + kg_search_settings: KGSearchSettings, + rag_generation_config: GenerationConfig, + task_prompt_override: Optional[str], + include_title_if_available: bool, + user: UserResponse, + ) -> dict: + return { + "messages": [message.to_dict() for message in messages], + "vector_search_settings": vector_search_settings.to_dict(), + "kg_search_settings": kg_search_settings.to_dict(), + "rag_generation_config": rag_generation_config.to_dict(), + "task_prompt_override": task_prompt_override, + "include_title_if_available": include_title_if_available, + "user": user.to_dict(), + } + + @staticmethod + def parse_agent_input(data: dict): + return { + "messages": [ + Message.from_dict(message) for message in data["messages"] + ], + "vector_search_settings": VectorSearchSettings.from_dict( + data["vector_search_settings"] + ), + "kg_search_settings": KGSearchSettings.from_dict( + data["kg_search_settings"] + ), + "rag_generation_config": GenerationConfig.from_dict( + data["rag_generation_config"] + ), + "task_prompt_override": data["task_prompt_override"], + "include_title_if_available": data["include_title_if_available"], + "user": RetrievalServiceAdapter._parse_user_data(data["user"]), + } diff --git a/py/core/pipelines/graph_enrichment.py b/py/core/pipelines/graph_enrichment.py index 03ff725cc..49d98dd78 100644 --- a/py/core/pipelines/graph_enrichment.py +++ b/py/core/pipelines/graph_enrichment.py @@ -27,7 +27,6 @@ def add_pipe( *args, **kwargs, ) -> None: - print("pipe = ", pipe) logger.debug( f"Adding pipe {pipe.config.name} to the KGEnrichmentPipeline" ) diff --git a/py/core/pipelines/ingestion_pipeline.py b/py/core/pipelines/ingestion_pipeline.py index 6cef2b187..84746f9df 100644 --- a/py/core/pipelines/ingestion_pipeline.py +++ b/py/core/pipelines/ingestion_pipeline.py @@ -32,7 +32,7 @@ async def run( state: Optional[AsyncState] = None, stream: bool = False, run_manager: Optional[RunManager] = None, - chunking_settings: Optional[ChunkingProvider] = None, + chunking_config: Optional[ChunkingProvider] = None, user: Optional[UserResponse] = None, *args: Any, **kwargs: Any, @@ -73,7 +73,7 @@ async def process_documents(): ), state, run_manager, - chunking_settings=chunking_settings, + chunking_config=chunking_config, *args, **kwargs, ): diff --git a/py/core/providers/database/group.py b/py/core/providers/database/group.py index feb682316..cee564e2e 100644 --- a/py/core/providers/database/group.py +++ b/py/core/providers/database/group.py @@ -453,7 +453,7 @@ def assign_document_to_group( except Exception as e: raise R2RException( status_code=500, - message="An error occurred while assigning the document to the group", + message=f"An error '{e}' occurred while assigning the document to the group", ) def document_groups( diff --git a/py/core/providers/database/relational.py b/py/core/providers/database/relational.py index a0ff0b311..4e99dae6e 100644 --- a/py/core/providers/database/relational.py +++ b/py/core/providers/database/relational.py @@ -1,6 +1,6 @@ import logging -from sqlalchemy import exc, text +from sqlalchemy import text from core.providers.database.base import DatabaseMixin, execute_query from core.providers.database.document import DocumentMixin diff --git a/py/core/providers/database/user.py b/py/core/providers/database/user.py index 2c6990906..578768e78 100644 --- a/py/core/providers/database/user.py +++ b/py/core/providers/database/user.py @@ -207,8 +207,6 @@ def delete_user(self, user_id: UUID) -> None: if not group_result: raise R2RException(status_code=404, message="User not found") - user_groups = group_result[0] - # Remove user from documents doc_update_query = f""" UPDATE {self._get_table_name('document_info')} diff --git a/py/core/providers/database/vecs/adapter/noop.py b/py/core/providers/database/vecs/adapter/noop.py index 26d35f28c..157dcb46e 100644 --- a/py/core/providers/database/vecs/adapter/noop.py +++ b/py/core/providers/database/vecs/adapter/noop.py @@ -6,7 +6,7 @@ All public classes, enums, and functions are re-exported by `vecs.adapters` module. """ -from typing import Any, Dict, Generator, Iterable, Optional, Tuple +from typing import Generator, Iterable, Optional from .base import AdapterContext, AdapterStep, Record diff --git a/py/core/providers/database/vecs/client.py b/py/core/providers/database/vecs/client.py index 6333e8103..50938f576 100644 --- a/py/core/providers/database/vecs/client.py +++ b/py/core/providers/database/vecs/client.py @@ -60,14 +60,14 @@ def __init__( max_retries: int = 3, retry_delay: int = 1, ): - self.service = create_engine( + self.engine = create_engine( connection_string, pool_size=pool_size, poolclass=QueuePool, pool_recycle=300, # Recycle connections after 5 min ) self.meta = MetaData(schema="vecs") - self.Session = sessionmaker(self.service) + self.Session = sessionmaker(self.engine) self.max_retries = max_retries self.retry_delay = retry_delay self.vector_version: Optional[str] = None @@ -283,7 +283,7 @@ def disconnect(self) -> None: Returns: None """ - self.service.dispose() + self.engine.dispose() logger.info("Disconnected from the database.") return diff --git a/py/core/providers/database/vecs/collection.py b/py/core/providers/database/vecs/collection.py index e1f09f18a..63345ecb0 100644 --- a/py/core/providers/database/vecs/collection.py +++ b/py/core/providers/database/vecs/collection.py @@ -11,19 +11,9 @@ import warnings from dataclasses import dataclass from enum import Enum -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Iterable, - List, - Optional, - Tuple, - Union, -) +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Union from uuid import UUID, uuid4 -import psycopg2 import sqlalchemy as sa from flupy import flu from nltk.corpus import wordnet @@ -32,13 +22,10 @@ Column, Index, MetaData, - String, Table, - alias, and_, cast, delete, - distinct, func, or_, select, @@ -57,7 +44,6 @@ CollectionNotFound, FilterError, MismatchedDimension, - Unreachable, ) if TYPE_CHECKING: diff --git a/py/core/providers/database/vector.py b/py/core/providers/database/vector.py index 26e11c370..f919b7d51 100644 --- a/py/core/providers/database/vector.py +++ b/py/core/providers/database/vector.py @@ -1,7 +1,6 @@ import logging import os from typing import Any, Optional -from uuid import UUID from sqlalchemy import text from sqlalchemy.engine.url import make_url @@ -484,23 +483,12 @@ def get_document_chunks( """ ) - count_query = text( - f""" - SELECT COUNT(*) - FROM vecs."{table_name}" - WHERE document_id = :document_id - """ - ) - params = {"document_id": document_id, "offset": offset} if limit != -1: params["limit"] = limit with self.vx.Session() as sess: results = sess.execute(query, params).fetchall() - total_count = sess.execute( - count_query, {"document_id": document_id} - ).scalar() return [ { diff --git a/py/core/providers/embeddings/litellm.py b/py/core/providers/embeddings/litellm.py index 6edc17b01..880cf5b3f 100644 --- a/py/core/providers/embeddings/litellm.py +++ b/py/core/providers/embeddings/litellm.py @@ -1,4 +1,3 @@ -import asyncio import logging from typing import Any, List diff --git a/py/core/providers/llm/litellm.py b/py/core/providers/llm/litellm.py index c6db63880..7957d32f4 100644 --- a/py/core/providers/llm/litellm.py +++ b/py/core/providers/llm/litellm.py @@ -51,11 +51,8 @@ async def _execute_task(self, task: dict[str, Any]): args["messages"] = messages args = {**args, **kwargs} - try: - response = await self.acompletion(**args) - return response - except Exception as e: - raise + response = await self.acompletion(**args) + return response def _execute_task_sync(self, task: dict[str, Any]): messages = task["messages"] diff --git a/py/core/providers/parsing/unstructured_parsing.py b/py/core/providers/parsing/unstructured_parsing.py index 51d489b4e..14930ee43 100644 --- a/py/core/providers/parsing/unstructured_parsing.py +++ b/py/core/providers/parsing/unstructured_parsing.py @@ -27,7 +27,6 @@ def __init__(self, use_api, config): try: from unstructured_client import UnstructuredClient from unstructured_client.models import operations, shared - from unstructured_client.models.errors import SDKError except ImportError: raise ImportError( diff --git a/py/poetry.lock b/py/poetry.lock index 414798940..fcdf26392 100644 --- a/py/poetry.lock +++ b/py/poetry.lock @@ -4,7 +4,7 @@ name = "aiohappyeyeballs" version = "2.4.0" description = "Happy Eyeballs for asyncio" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, @@ -15,7 +15,7 @@ files = [ name = "aiohttp" version = "3.10.5" description = "Async http client/server framework (asyncio)" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, @@ -123,11 +123,25 @@ yarl = ">=1.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +[[package]] +name = "aiohttp-retry" +version = "2.8.3" +description = "Simple retry client for aiohttp" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiohttp_retry-2.8.3-py3-none-any.whl", hash = "sha256:3aeeead8f6afe48272db93ced9440cf4eda8b6fd7ee2abb25357b7eb28525b45"}, + {file = "aiohttp_retry-2.8.3.tar.gz", hash = "sha256:9a8e637e31682ad36e1ff9f8bcba912fcfc7d7041722bc901a4b948da4d71ea9"}, +] + +[package.dependencies] +aiohttp = "*" + [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, @@ -155,6 +169,20 @@ typing_extensions = ">=4.0" dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"] docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] +[[package]] +name = "aiostream" +version = "0.5.2" +description = "Generator-based operators for asynchronous iteration" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiostream-0.5.2-py3-none-any.whl", hash = "sha256:054660370be9d37f6fe3ece3851009240416bd082e469fd90cc8673d3818cf71"}, + {file = "aiostream-0.5.2.tar.gz", hash = "sha256:b71b519a2d66c38f0872403ab86417955b77352f08d9ad02ad46fc3926b389f4"}, +] + +[package.dependencies] +typing-extensions = "*" + [[package]] name = "annotated-types" version = "0.7.0" @@ -206,7 +234,7 @@ six = "*" name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, @@ -274,7 +302,7 @@ test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] name = "attrs" version = "24.2.0" description = "Classes Without Boilerplate" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, @@ -616,66 +644,87 @@ files = [ [[package]] name = "contourpy" -version = "1.2.1" +version = "1.3.0" description = "Python library for calculating contours of 2D quadrilateral grids" optional = true python-versions = ">=3.9" files = [ - {file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"}, - {file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b"}, - {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd"}, - {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619"}, - {file = "contourpy-1.2.1-cp310-cp310-win32.whl", hash = "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8"}, - {file = "contourpy-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9"}, - {file = "contourpy-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5"}, - {file = "contourpy-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df"}, - {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205"}, - {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8"}, - {file = "contourpy-1.2.1-cp311-cp311-win32.whl", hash = "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec"}, - {file = "contourpy-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922"}, - {file = "contourpy-1.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc"}, - {file = "contourpy-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b"}, - {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce"}, - {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4"}, - {file = "contourpy-1.2.1-cp312-cp312-win32.whl", hash = "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f"}, - {file = "contourpy-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce"}, - {file = "contourpy-1.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b"}, - {file = "contourpy-1.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445"}, - {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02"}, - {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083"}, - {file = "contourpy-1.2.1-cp39-cp39-win32.whl", hash = "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba"}, - {file = "contourpy-1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f"}, - {file = "contourpy-1.2.1.tar.gz", hash = "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, + {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, + {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, + {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, + {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, + {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, + {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, + {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, + {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, + {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, + {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, + {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, ] [package.dependencies] -numpy = ">=1.20" +numpy = ">=1.23" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] [[package]] name = "coverage" @@ -1057,7 +1106,7 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] name = "frozenlist" version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, @@ -1226,13 +1275,13 @@ test-win = ["POT", "pytest", "pytest-cov", "testfixtures"] [[package]] name = "google-api-core" -version = "2.19.1" +version = "2.19.2" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, - {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, + {file = "google_api_core-2.19.2-py3-none-any.whl", hash = "sha256:53ec0258f2837dd53bbd3d3df50f5359281b3cc13f800c941dd15a9b5a415af4"}, + {file = "google_api_core-2.19.2.tar.gz", hash = "sha256:ca07de7e8aa1c98a8bfca9321890ad2340ef7f2eb136e558cee68f24b94b0a8f"}, ] [package.dependencies] @@ -1298,13 +1347,13 @@ protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4 [[package]] name = "googleapis-common-protos" -version = "1.64.0" +version = "1.65.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis_common_protos-1.64.0-py2.py3-none-any.whl", hash = "sha256:d1bfc569f70ed2e96ccf06ead265c2cf42b5abfc817cda392e3835f3b67b5c59"}, - {file = "googleapis_common_protos-1.64.0.tar.gz", hash = "sha256:7d77ca6b7c0c38eb6b1bab3b4c9973acf57ce4f2a6d3a4136acba10bcbfb3025"}, + {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, + {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, ] [package.dependencies] @@ -1503,19 +1552,81 @@ protobuf = ["grpcio-tools (>=1.66.0)"] [[package]] name = "grpcio-status" -version = "1.66.0" +version = "1.62.3" description = "Status proto mapping for gRPC" optional = false -python-versions = ">=3.8" +python-versions = ">=3.6" files = [ - {file = "grpcio_status-1.66.0-py3-none-any.whl", hash = "sha256:fd89c8ebcb87eea743327b24e31abb1b4e758cd6e6ede15bfb334c33e661384d"}, - {file = "grpcio_status-1.66.0.tar.gz", hash = "sha256:c246b46c15295068fa36fc4b0b4a43f9463b75967b5a8d053f5e1d56e7c94b6e"}, + {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"}, + {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"}, ] [package.dependencies] googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.66.0" -protobuf = ">=5.26.1,<6.0dev" +grpcio = ">=1.62.3" +protobuf = ">=4.21.6" + +[[package]] +name = "grpcio-tools" +version = "1.62.3" +description = "Protobuf code generator for gRPC" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-tools-1.62.3.tar.gz", hash = "sha256:7c7136015c3d62c3eef493efabaf9e3380e3e66d24ee8e94c01cb71377f57833"}, + {file = "grpcio_tools-1.62.3-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2f968b049c2849540751ec2100ab05e8086c24bead769ca734fdab58698408c1"}, + {file = "grpcio_tools-1.62.3-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0a8c0c4724ae9c2181b7dbc9b186df46e4f62cb18dc184e46d06c0ebeccf569e"}, + {file = "grpcio_tools-1.62.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5782883a27d3fae8c425b29a9d3dcf5f47d992848a1b76970da3b5a28d424b26"}, + {file = "grpcio_tools-1.62.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d812daffd0c2d2794756bd45a353f89e55dc8f91eb2fc840c51b9f6be62667"}, + {file = "grpcio_tools-1.62.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b47d0dda1bdb0a0ba7a9a6de88e5a1ed61f07fad613964879954961e36d49193"}, + {file = "grpcio_tools-1.62.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ca246dffeca0498be9b4e1ee169b62e64694b0f92e6d0be2573e65522f39eea9"}, + {file = "grpcio_tools-1.62.3-cp310-cp310-win32.whl", hash = "sha256:6a56d344b0bab30bf342a67e33d386b0b3c4e65868ffe93c341c51e1a8853ca5"}, + {file = "grpcio_tools-1.62.3-cp310-cp310-win_amd64.whl", hash = "sha256:710fecf6a171dcbfa263a0a3e7070e0df65ba73158d4c539cec50978f11dad5d"}, + {file = "grpcio_tools-1.62.3-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:703f46e0012af83a36082b5f30341113474ed0d91e36640da713355cd0ea5d23"}, + {file = "grpcio_tools-1.62.3-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:7cc83023acd8bc72cf74c2edbe85b52098501d5b74d8377bfa06f3e929803492"}, + {file = "grpcio_tools-1.62.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ff7d58a45b75df67d25f8f144936a3e44aabd91afec833ee06826bd02b7fbe7"}, + {file = "grpcio_tools-1.62.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f2483ea232bd72d98a6dc6d7aefd97e5bc80b15cd909b9e356d6f3e326b6e43"}, + {file = "grpcio_tools-1.62.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:962c84b4da0f3b14b3cdb10bc3837ebc5f136b67d919aea8d7bb3fd3df39528a"}, + {file = "grpcio_tools-1.62.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8ad0473af5544f89fc5a1ece8676dd03bdf160fb3230f967e05d0f4bf89620e3"}, + {file = "grpcio_tools-1.62.3-cp311-cp311-win32.whl", hash = "sha256:db3bc9fa39afc5e4e2767da4459df82b095ef0cab2f257707be06c44a1c2c3e5"}, + {file = "grpcio_tools-1.62.3-cp311-cp311-win_amd64.whl", hash = "sha256:e0898d412a434e768a0c7e365acabe13ff1558b767e400936e26b5b6ed1ee51f"}, + {file = "grpcio_tools-1.62.3-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d102b9b21c4e1e40af9a2ab3c6d41afba6bd29c0aa50ca013bf85c99cdc44ac5"}, + {file = "grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:0a52cc9444df978438b8d2332c0ca99000521895229934a59f94f37ed896b133"}, + {file = "grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141d028bf5762d4a97f981c501da873589df3f7e02f4c1260e1921e565b376fa"}, + {file = "grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47a5c093ab256dec5714a7a345f8cc89315cb57c298b276fa244f37a0ba507f0"}, + {file = "grpcio_tools-1.62.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f6831fdec2b853c9daa3358535c55eed3694325889aa714070528cf8f92d7d6d"}, + {file = "grpcio_tools-1.62.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e02d7c1a02e3814c94ba0cfe43d93e872c758bd8fd5c2797f894d0c49b4a1dfc"}, + {file = "grpcio_tools-1.62.3-cp312-cp312-win32.whl", hash = "sha256:b881fd9505a84457e9f7e99362eeedd86497b659030cf57c6f0070df6d9c2b9b"}, + {file = "grpcio_tools-1.62.3-cp312-cp312-win_amd64.whl", hash = "sha256:11c625eebefd1fd40a228fc8bae385e448c7e32a6ae134e43cf13bbc23f902b7"}, + {file = "grpcio_tools-1.62.3-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:ec6fbded0c61afe6f84e3c2a43e6d656791d95747d6d28b73eff1af64108c434"}, + {file = "grpcio_tools-1.62.3-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:bfda6ee8990997a9df95c5606f3096dae65f09af7ca03a1e9ca28f088caca5cf"}, + {file = "grpcio_tools-1.62.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b77f9f9cee87cd798f0fe26b7024344d1b03a7cd2d2cba7035f8433b13986325"}, + {file = "grpcio_tools-1.62.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e02d3b96f2d0e4bab9ceaa30f37d4f75571e40c6272e95364bff3125a64d184"}, + {file = "grpcio_tools-1.62.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1da38070738da53556a4b35ab67c1b9884a5dd48fa2f243db35dc14079ea3d0c"}, + {file = "grpcio_tools-1.62.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ace43b26d88a58dcff16c20d23ff72b04d0a415f64d2820f4ff06b1166f50557"}, + {file = "grpcio_tools-1.62.3-cp37-cp37m-win_amd64.whl", hash = "sha256:350a80485e302daaa95d335a931f97b693e170e02d43767ab06552c708808950"}, + {file = "grpcio_tools-1.62.3-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:c3a1ac9d394f8e229eb28eec2e04b9a6f5433fa19c9d32f1cb6066e3c5114a1d"}, + {file = "grpcio_tools-1.62.3-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:11f363570dea661dde99e04a51bd108a5807b5df32a6f8bdf4860e34e94a4dbf"}, + {file = "grpcio_tools-1.62.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9ad9950119d8ae27634e68b7663cc8d340ae535a0f80d85a55e56a6973ab1f"}, + {file = "grpcio_tools-1.62.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c5d22b252dcef11dd1e0fbbe5bbfb9b4ae048e8880d33338215e8ccbdb03edc"}, + {file = "grpcio_tools-1.62.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:27cd9ef5c5d68d5ed104b6dcb96fe9c66b82050e546c9e255716903c3d8f0373"}, + {file = "grpcio_tools-1.62.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f4b1615adf67bd8bb71f3464146a6f9949972d06d21a4f5e87e73f6464d97f57"}, + {file = "grpcio_tools-1.62.3-cp38-cp38-win32.whl", hash = "sha256:e18e15287c31baf574fcdf8251fb7f997d64e96c6ecf467906e576da0a079af6"}, + {file = "grpcio_tools-1.62.3-cp38-cp38-win_amd64.whl", hash = "sha256:6c3064610826f50bd69410c63101954676edc703e03f9e8f978a135f1aaf97c1"}, + {file = "grpcio_tools-1.62.3-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:8e62cc7164b0b7c5128e637e394eb2ef3db0e61fc798e80c301de3b2379203ed"}, + {file = "grpcio_tools-1.62.3-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:c8ad5cce554e2fcaf8842dee5d9462583b601a3a78f8b76a153c38c963f58c10"}, + {file = "grpcio_tools-1.62.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec279dcf3518201fc592c65002754f58a6b542798cd7f3ecd4af086422f33f29"}, + {file = "grpcio_tools-1.62.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c989246c2aebc13253f08be32538a4039a64e12d9c18f6d662d7aee641dc8b5"}, + {file = "grpcio_tools-1.62.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ca4f5eeadbb57cf03317d6a2857823239a63a59cc935f5bd6cf6e8b7af7a7ecc"}, + {file = "grpcio_tools-1.62.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0cb3a3436ac119cbd37a7d3331d9bdf85dad21a6ac233a3411dff716dcbf401e"}, + {file = "grpcio_tools-1.62.3-cp39-cp39-win32.whl", hash = "sha256:3eae6ea76d62fcac091e1f15c2dcedf1dc3f114f8df1a972a8a0745e89f4cf61"}, + {file = "grpcio_tools-1.62.3-cp39-cp39-win_amd64.whl", hash = "sha256:eec73a005443061f4759b71a056f745e3b000dc0dc125c9f20560232dfbcbd14"}, +] + +[package.dependencies] +grpcio = ">=1.62.3" +protobuf = ">=4.21.6,<5.0dev" +setuptools = "*" [[package]] name = "gunicorn" @@ -1548,6 +1659,33 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "hatchet-sdk" +version = "0.36.11" +description = "" +optional = false +python-versions = "<4.0,>=3.10" +files = [ + {file = "hatchet_sdk-0.36.11-py3-none-any.whl", hash = "sha256:15de7caf39d523feab76ec330e0e5cc301567be2af9d4ba611d65736554941b2"}, + {file = "hatchet_sdk-0.36.11.tar.gz", hash = "sha256:0d17cea648f3d549113a54d437395b54e9426ce648d5c4a8897e0247586dd609"}, +] + +[package.dependencies] +aiohttp = ">=3.10.5,<4.0.0" +aiohttp-retry = ">=2.8.3,<3.0.0" +aiostream = ">=0.5.2,<0.6.0" +grpcio = ">=1.64.1,<2.0.0" +grpcio-tools = ">=1.60.0,<2.0.0" +loguru = ">=0.7.2,<0.8.0" +nest-asyncio = ">=1.6.0,<2.0.0" +protobuf = ">=4.25.2,<5.0.0" +pydantic = ">=2.6.3,<3.0.0" +python-dateutil = ">=2.9.0.post0,<3.0.0" +python-dotenv = ">=1.0.0,<2.0.0" +pyyaml = ">=6.0.1,<7.0.0" +tenacity = ">=8.0.0" +urllib3 = ">=2.2.1,<3.0.0" + [[package]] name = "httpcore" version = "1.0.5" @@ -1740,28 +1878,6 @@ doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linke perf = ["ipython"] test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] -[[package]] -name = "importlib-resources" -version = "6.4.4" -description = "Read resources from Python packages" -optional = true -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.4.4-py3-none-any.whl", hash = "sha256:dda242603d1c9cd836c3368b1174ed74cb4049ecd209e7a1a0104620c18c5c11"}, - {file = "importlib_resources-6.4.4.tar.gz", hash = "sha256:20600c8b7361938dc0bb2d5ec0297802e575df486f5a544fa414da65e13721f7"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] -type = ["pytest-mypy"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -2094,6 +2210,24 @@ files = [ {file = "llvmlite-0.43.0.tar.gz", hash = "sha256:ae2b5b5c3ef67354824fb75517c8db5fbe93bc02cd9671f3c62271626bc041d5"}, ] +[[package]] +name = "loguru" +version = "0.7.2" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = ">=3.5" +files = [ + {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, + {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] + [[package]] name = "lxml" version = "5.3.0" @@ -2259,9 +2393,6 @@ files = [ {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, ] -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - [package.extras] docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] @@ -2388,7 +2519,6 @@ files = [ contourpy = ">=1.0.1" cycler = ">=0.10" fonttools = ">=4.22.0" -importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} kiwisolver = ">=1.3.1" numpy = ">=1.23" packaging = ">=20.0" @@ -2449,7 +2579,7 @@ test = ["coverage (<5.0)", "coveralls (>=1.1,<2.0)", "pytest (>=3.0.0,<4.0)", "p name = "multidict" version = "6.0.5" description = "multidict implementation" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, @@ -2634,20 +2764,20 @@ files = [ [[package]] name = "networkx" -version = "3.2.1" +version = "3.3" description = "Python package for creating and manipulating graphs and networks" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, - {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, + {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, + {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, ] [package.extras] -default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] +default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] @@ -2767,13 +2897,13 @@ files = [ [[package]] name = "ollama" -version = "0.3.1" +version = "0.3.2" description = "The official Python client for Ollama." optional = true python-versions = "<4.0,>=3.8" files = [ - {file = "ollama-0.3.1-py3-none-any.whl", hash = "sha256:db50034c73d6350349bdfba19c3f0d54a3cea73eb97b35f9d7419b2fc7206454"}, - {file = "ollama-0.3.1.tar.gz", hash = "sha256:032572fb494a4fba200c65013fe937a65382c846b5f358d9e8918ecbc9ac44b5"}, + {file = "ollama-0.3.2-py3-none-any.whl", hash = "sha256:ed2a6f752bd91c49b477d84a259c5657785d7777689d4a27ffe0a4d5b5dd3cae"}, + {file = "ollama-0.3.2.tar.gz", hash = "sha256:7deb3287cdefa1c39cc046163096f8597b83f59ca31a1f8ae78e71eccb7af95f"}, ] [package.dependencies] @@ -2821,12 +2951,10 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.21.0", markers = "python_version == \"3.9\" and platform_system == \"Darwin\" and platform_machine == \"arm64\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, - {version = ">=1.19.3", markers = "platform_system == \"Linux\" and platform_machine == \"aarch64\" and python_version >= \"3.8\" and python_version < \"3.10\" or python_version > \"3.9\" and python_version < \"3.10\" or python_version >= \"3.9\" and platform_system != \"Darwin\" and python_version < \"3.10\" or python_version >= \"3.9\" and platform_machine != \"arm64\" and python_version < \"3.10\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] [[package]] @@ -2909,8 +3037,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -3147,13 +3275,13 @@ dev = ["Click (>=7.0)", "Sphinx (>=2.2.1)"] [[package]] name = "posthog" -version = "3.5.2" +version = "3.6.0" description = "Integrate PostHog into any python application." optional = true python-versions = "*" files = [ - {file = "posthog-3.5.2-py2.py3-none-any.whl", hash = "sha256:605b3d92369971cc99290b1fcc8534cbddac3726ef7972caa993454a5ecfb644"}, - {file = "posthog-3.5.2.tar.gz", hash = "sha256:a383a80c1f47e0243f5ce359e81e06e2e7b37eb39d1d6f8d01c3e64ed29df2ee"}, + {file = "posthog-3.6.0-py2.py3-none-any.whl", hash = "sha256:6f8dacc6d14d80734b1d15bd4ab08b049629c5f0fc420cafcf1ce0667c76c83c"}, + {file = "posthog-3.6.0.tar.gz", hash = "sha256:27dbf537241a69fb5f6a3e9561caa2d555d5891d95fa65c27ffa6b52d1fb63b6"}, ] [package.dependencies] @@ -3283,22 +3411,22 @@ testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "5.27.3" +version = "4.25.4" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.27.3-cp310-abi3-win32.whl", hash = "sha256:dcb307cd4ef8fec0cf52cb9105a03d06fbb5275ce6d84a6ae33bc6cf84e0a07b"}, - {file = "protobuf-5.27.3-cp310-abi3-win_amd64.whl", hash = "sha256:16ddf3f8c6c41e1e803da7abea17b1793a97ef079a912e42351eabb19b2cffe7"}, - {file = "protobuf-5.27.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:68248c60d53f6168f565a8c76dc58ba4fa2ade31c2d1ebdae6d80f969cdc2d4f"}, - {file = "protobuf-5.27.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:b8a994fb3d1c11156e7d1e427186662b64694a62b55936b2b9348f0a7c6625ce"}, - {file = "protobuf-5.27.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:a55c48f2a2092d8e213bd143474df33a6ae751b781dd1d1f4d953c128a415b25"}, - {file = "protobuf-5.27.3-cp38-cp38-win32.whl", hash = "sha256:043853dcb55cc262bf2e116215ad43fa0859caab79bb0b2d31b708f128ece035"}, - {file = "protobuf-5.27.3-cp38-cp38-win_amd64.whl", hash = "sha256:c2a105c24f08b1e53d6c7ffe69cb09d0031512f0b72f812dd4005b8112dbe91e"}, - {file = "protobuf-5.27.3-cp39-cp39-win32.whl", hash = "sha256:c84eee2c71ed83704f1afbf1a85c3171eab0fd1ade3b399b3fad0884cbcca8bf"}, - {file = "protobuf-5.27.3-cp39-cp39-win_amd64.whl", hash = "sha256:af7c0b7cfbbb649ad26132e53faa348580f844d9ca46fd3ec7ca48a1ea5db8a1"}, - {file = "protobuf-5.27.3-py3-none-any.whl", hash = "sha256:8572c6533e544ebf6899c360e91d6bcbbee2549251643d32c52cf8a5de295ba5"}, - {file = "protobuf-5.27.3.tar.gz", hash = "sha256:82460903e640f2b7e34ee81a947fdaad89de796d324bcbc38ff5430bcdead82c"}, + {file = "protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4"}, + {file = "protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d"}, + {file = "protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b"}, + {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835"}, + {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040"}, + {file = "protobuf-4.25.4-cp38-cp38-win32.whl", hash = "sha256:7e372cbbda66a63ebca18f8ffaa6948455dfecc4e9c1029312f6c2edcd86c4e1"}, + {file = "protobuf-4.25.4-cp38-cp38-win_amd64.whl", hash = "sha256:051e97ce9fa6067a4546e75cb14f90cf0232dcb3e3d508c448b8d0e4265b61c1"}, + {file = "protobuf-4.25.4-cp39-cp39-win32.whl", hash = "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca"}, + {file = "protobuf-4.25.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f"}, + {file = "protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978"}, + {file = "protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d"}, ] [[package]] @@ -3742,7 +3870,7 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" -optional = true +optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, @@ -4263,25 +4391,29 @@ stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] [[package]] name = "setuptools" -version = "73.0.1" +version = "74.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-73.0.1-py3-none-any.whl", hash = "sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e"}, - {file = "setuptools-73.0.1.tar.gz", hash = "sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193"}, + {file = "setuptools-74.0.0-py3-none-any.whl", hash = "sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f"}, + {file = "setuptools-74.0.0.tar.gz", hash = "sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e"}, ] [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -4435,7 +4567,6 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] @@ -4485,6 +4616,21 @@ build = ["cython (>=0.29.33)"] develop = ["colorama", "cython (>=0.29.33)", "cython (>=3.0.10,<4)", "flake8", "isort", "joblib", "matplotlib (>=3)", "pytest (>=7.3.0,<8)", "pytest-cov", "pytest-randomly", "pytest-xdist", "pywinpty", "setuptools-scm[toml] (>=8.0,<9.0)"] docs = ["ipykernel", "jupyter-client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"] +[[package]] +name = "tenacity" +version = "9.0.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, + {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + [[package]] name = "termcolor" version = "2.4.0" @@ -4859,6 +5005,20 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +optional = false +python-versions = ">=3.5" +files = [ + {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, + {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, +] + +[package.extras] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] + [[package]] name = "wrapt" version = "1.16.0" @@ -4953,7 +5113,7 @@ files = [ name = "yarl" version = "1.9.4" description = "Yet another URL library" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, @@ -5077,5 +5237,5 @@ core-ingest-movies = ["moviepy", "opencv-python"] [metadata] lock-version = "2.0" -python-versions = ">=3.9,<3.13" -content-hash = "82427d2321202e30b644c597b8d5276a882a480bc48f4f180e3d7989b8aa0c0b" +python-versions = ">=3.10,<3.13" +content-hash = "2b6aaba5fc01997dfc01479bf5b61c22d6c71a00e80dfb473ada60955fcbf279" diff --git a/py/pyproject.toml b/py/pyproject.toml index a1a5c9982..9354643cd 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -19,7 +19,7 @@ packages = [ [tool.poetry.dependencies] # Python Versions -python = ">=3.9,<3.13" +python = ">=3.10,<3.13" httpx = "^0.27.0" nest-asyncio = "^1.6.0" @@ -66,6 +66,7 @@ deepdiff = { version = "^7.0.1", optional = true } graspologic = { version = "^3.4.1", optional = true } google-cloud-tasks = "^2.16.5" google-auth = "^2.34.0" +hatchet-sdk = "^0.36.10" [tool.poetry.extras] core = [ diff --git a/py/r2r/__init__.py b/py/r2r/__init__.py index d568f22a2..e585b1aa8 100644 --- a/py/r2r/__init__.py +++ b/py/r2r/__init__.py @@ -6,11 +6,16 @@ "R2RClient", ] -try: - import core - from core import * +import core +from core import * - __all__ += core.__all__ -except ImportError: - # Core dependencies not installed - pass +__all__ += core.__all__ + +# try: +# import core +# from core import * + +# __all__ += core.__all__ +# except ImportError: +# # Core dependencies not installed +# pass diff --git a/py/sdk/client.py b/py/sdk/client.py index 8c8d38d3b..5bc066d59 100644 --- a/py/sdk/client.py +++ b/py/sdk/client.py @@ -122,7 +122,7 @@ async def _make_request(self, method, endpoint, **kwargs): headers.update(self._get_auth_header()) if ( kwargs.get("params", None) == {} - or kwargs.get("params", None) == None + or kwargs.get("params", None) is None ): if "params" in kwargs: kwargs.pop("params") diff --git a/py/sdk/ingestion.py b/py/sdk/ingestion.py index 0f58eb5df..c5403048e 100644 --- a/py/sdk/ingestion.py +++ b/py/sdk/ingestion.py @@ -15,7 +15,7 @@ async def ingest_files( file_paths: list[str], document_ids: Optional[list[Union[str, UUID]]] = None, metadatas: Optional[list[dict]] = None, - chunking_settings: Optional[Union[dict, ChunkingConfig]] = None, + chunking_config: Optional[Union[dict, ChunkingConfig]] = None, ) -> dict: """ Ingest files into your R2R deployment @@ -24,7 +24,7 @@ async def ingest_files( file_paths (List[str]): List of file paths to ingest. document_ids (Optional[List[str]]): List of document IDs. metadatas (Optional[List[dict]]): List of metadata dictionaries for each file. - chunking_settings (Optional[Union[dict, ChunkingConfig]]): Custom chunking configuration. + chunking_config (Optional[Union[dict, ChunkingConfig]]): Custom chunking configuration. Returns: dict: Ingestion results containing processed, failed, and skipped documents. @@ -38,11 +38,11 @@ async def ingest_files( "Number of metadatas must match number of document IDs." ) if ( - chunking_settings is not None - and chunking_settings is not ChunkingConfig + chunking_config is not None + and chunking_config is not ChunkingConfig ): # check if the provided dict maps to a ChunkingConfig - ChunkingConfig(**chunking_settings) + ChunkingConfig(**chunking_config) all_file_paths = [] for path in file_paths: @@ -74,13 +74,13 @@ async def ingest_files( if document_ids else None ), - "chunking_settings": ( + "chunking_config": ( json.dumps( - chunking_settings.model_dump() - if isinstance(chunking_settings, ChunkingConfig) - else chunking_settings + chunking_config.model_dump() + if isinstance(chunking_config, ChunkingConfig) + else chunking_config ) - if chunking_settings + if chunking_config else None ), } @@ -94,7 +94,7 @@ async def update_files( file_paths: list[str], document_ids: Optional[list[Union[str, UUID]]] = None, metadatas: Optional[list[dict]] = None, - chunking_settings: Optional[Union[dict, ChunkingConfig]] = None, + chunking_config: Optional[Union[dict, ChunkingConfig]] = None, ) -> dict: """ Update existing files in your R2R deployment. @@ -103,7 +103,7 @@ async def update_files( file_paths (List[str]): List of file paths to update. document_ids (List[str]): List of document IDs to update. metadatas (Optional[List[dict]]): List of updated metadata dictionaries for each file. - chunking_settings (Optional[Union[dict, ChunkingConfig]]): Custom chunking configuration. + chunking_config (Optional[Union[dict, ChunkingConfig]]): Custom chunking configuration. Returns: dict: Update results containing processed, failed, and skipped documents. @@ -137,11 +137,11 @@ async def update_files( ) if metadatas: data["metadatas"] = json.dumps(metadatas) - if chunking_settings: - data["chunking_settings"] = ( - chunking_settings.model_dump() - if isinstance(chunking_settings, ChunkingConfig) - else chunking_settings + if chunking_config: + data["chunking_config"] = ( + chunking_config.model_dump() + if isinstance(chunking_config, ChunkingConfig) + else chunking_config ) return await client._make_request( diff --git a/py/tests/test_kg.py b/py/tests/test_kg.py index 1bd8e5d35..c0b0bda87 100644 --- a/py/tests/test_kg.py +++ b/py/tests/test_kg.py @@ -53,7 +53,7 @@ async def test_extract_kg_success(kg_extraction_pipe, document_fragment): assert isinstance(result, KGExtraction) assert len(result.entities) == 1 assert len(result.triples) == 1 - assert result.entities['Entity1'].name == "Entity1" + assert result.entities["Entity1"].name == "Entity1" assert result.triples[0].subject == "Entity1" assert result.triples[0].object == "Entity2" @@ -71,7 +71,7 @@ async def mock_input_generator(): return_value=KGExtraction( fragment_id=document_fragment.id, document_id=document_fragment.document_id, - entities={ + entities={ "TestEntity": Entity( name="TestEntity", category="TestCategory",