diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 19b3d6cb3..ec4b56300 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -17,7 +17,7 @@ jobs: publish_docker_to_ecr: name: Publish Cognee Docker image - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: id-token: write contents: read diff --git a/.github/workflows/cd_prd.yaml b/.github/workflows/cd_prd.yaml index fc40d8884..15c046215 100644 --- a/.github/workflows/cd_prd.yaml +++ b/.github/workflows/cd_prd.yaml @@ -17,7 +17,7 @@ jobs: publish_docker_to_ecr: name: Publish Docker PromethAI image - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: id-token: write contents: read diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f9acd1be0..7d63aa88d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: build_docker: name: Build Cognee Backend Docker App Image - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check out Cognee code uses: actions/checkout@v3 diff --git a/.github/workflows/community_greetings.yml b/.github/workflows/community_greetings.yml index b480ed111..a43f7f549 100644 --- a/.github/workflows/community_greetings.yml +++ b/.github/workflows/community_greetings.yml @@ -4,7 +4,7 @@ on: [pull_request, issues] jobs: greeting: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/first-interaction@v1 with: diff --git a/.github/workflows/docker_compose.yml b/.github/workflows/docker_compose.yml index 657f0c8c6..321cbd045 100644 --- a/.github/workflows/docker_compose.yml +++ b/.github/workflows/docker_compose.yml @@ -12,7 +12,7 @@ on: jobs: docker-compose-test: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout repository diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index a80f1f442..20f0bde96 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -7,7 +7,7 @@ on: jobs: docker-build-and-push: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout repository @@ -32,7 +32,7 @@ jobs: run: | IMAGE_NAME=cognee/cognee TAG_VERSION="${BRANCH_NAME}-${COMMIT_SHA}" - + echo "Building image: ${IMAGE_NAME}:${TAG_VERSION}" docker buildx build \ --platform linux/amd64,linux/arm64 \ diff --git a/.github/workflows/profiling.yaml b/.github/workflows/profiling.yaml index 93ed82f82..0ecbc960c 100644 --- a/.github/workflows/profiling.yaml +++ b/.github/workflows/profiling.yaml @@ -7,7 +7,7 @@ on: jobs: profiler: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: # Checkout the code from the repository with full history @@ -47,7 +47,7 @@ jobs: python-version: '3.10' - name: Install Poetry - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true @@ -57,6 +57,8 @@ jobs: run: | poetry install --no-interaction --all-extras poetry run pip install pyinstrument + poetry run pip install parso + poetry run pip install jedi # Set environment variables for SHAs @@ -80,50 +82,50 @@ jobs: poetry run pyinstrument --renderer json -o base_results.json cognee/api/v1/cognify/code_graph_pipeline.py # Run profiler on head branch - - name: Run profiler on head branch - env: - HEAD_SHA: ${{ env.HEAD_SHA }} - run: | - echo "Profiling the head branch for code_graph_pipeline.py" - echo "Checking out head SHA: $HEAD_SHA" - git checkout $HEAD_SHA - echo "This is the working directory: $PWD" - # Ensure the script is executable - chmod +x cognee/api/v1/cognify/code_graph_pipeline.py - # Run Scalene - poetry run pyinstrument --renderer json -o head_results.json cognee/api/v1/cognify/code_graph_pipeline.py - - # Compare profiling results - - name: Compare profiling results - run: | - python -c ' - import json - try: - with open("base_results.json") as f: - base = json.load(f) - with open("head_results.json") as f: - head = json.load(f) - cpu_diff = head.get("total_cpu_samples_python", 0) - base.get("total_cpu_samples_python", 0) - memory_diff = head.get("malloc_samples", 0) - base.get("malloc_samples", 0) - results = [ - f"CPU Usage Difference: {cpu_diff}", - f"Memory Usage Difference: {memory_diff} bytes" - ] - with open("profiling_diff.txt", "w") as f: - f.write("\\n".join(results) + "\\n") - print("\\n".join(results)) # Print results to terminal - except Exception as e: - error_message = f"Error comparing profiling results: {e}" - with open("profiling_diff.txt", "w") as f: - f.write(error_message + "\\n") - print(error_message) # Print error to terminal - ' - - - name: Upload profiling diff artifact - uses: actions/upload-artifact@v3 - with: - name: profiling-diff - path: profiling_diff.txt +# - name: Run profiler on head branch +# env: +# HEAD_SHA: ${{ env.HEAD_SHA }} +# run: | +# echo "Profiling the head branch for code_graph_pipeline.py" +# echo "Checking out head SHA: $HEAD_SHA" +# git checkout $HEAD_SHA +# echo "This is the working directory: $PWD" +# # Ensure the script is executable +# chmod +x cognee/api/v1/cognify/code_graph_pipeline.py +# # Run Scalene +# poetry run pyinstrument --renderer json -o head_results.json cognee/api/v1/cognify/code_graph_pipeline.py +# +# # Compare profiling results +# - name: Compare profiling results +# run: | +# python -c ' +# import json +# try: +# with open("base_results.json") as f: +# base = json.load(f) +# with open("head_results.json") as f: +# head = json.load(f) +# cpu_diff = head.get("total_cpu_samples_python", 0) - base.get("total_cpu_samples_python", 0) +# memory_diff = head.get("malloc_samples", 0) - base.get("malloc_samples", 0) +# results = [ +# f"CPU Usage Difference: {cpu_diff}", +# f"Memory Usage Difference: {memory_diff} bytes" +# ] +# with open("profiling_diff.txt", "w") as f: +# f.write("\\n".join(results) + "\\n") +# print("\\n".join(results)) # Print results to terminal +# except Exception as e: +# error_message = f"Error comparing profiling results: {e}" +# with open("profiling_diff.txt", "w") as f: +# f.write(error_message + "\\n") +# print(error_message) # Print error to terminal +# ' +# +# - name: Upload profiling diff artifact +# uses: actions/upload-artifact@v3 +# with: +# name: profiling-diff +# path: profiling_diff.txt # Post results to the pull request # - name: Post profiling results to PR diff --git a/.github/workflows/py_lint.yml b/.github/workflows/py_lint.yml index dfdcdfd8f..543a0d221 100644 --- a/.github/workflows/py_lint.yml +++ b/.github/workflows/py_lint.yml @@ -16,8 +16,8 @@ jobs: fail-fast: true matrix: os: - - ubuntu-latest - python-version: ["3.8.x", "3.9.x", "3.10.x", "3.11.x"] + - ubuntu-22.04 + python-version: ["3.10.x", "3.11.x"] defaults: run: diff --git a/.github/workflows/release_discord_action.yml b/.github/workflows/release_discord_action.yml index 7b3ae845d..f3113ccb7 100644 --- a/.github/workflows/release_discord_action.yml +++ b/.github/workflows/release_discord_action.yml @@ -6,7 +6,7 @@ on: jobs: github-releases-to-discord: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/reusable_notebook.yml b/.github/workflows/reusable_notebook.yml index 0c0b63ec4..8034aca97 100644 --- a/.github/workflows/reusable_notebook.yml +++ b/.github/workflows/reusable_notebook.yml @@ -22,7 +22,7 @@ jobs: run_notebook_test: name: test - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 defaults: run: shell: bash @@ -36,7 +36,7 @@ jobs: python-version: '3.11.x' - name: Install Poetry - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true @@ -58,4 +58,4 @@ jobs: --to notebook \ --execute ${{ inputs.notebook-location }} \ --output executed_notebook.ipynb \ - --ExecutePreprocessor.timeout=1200 \ No newline at end of file + --ExecutePreprocessor.timeout=1200 diff --git a/.github/workflows/reusable_python_example.yml b/.github/workflows/reusable_python_example.yml index 03f928656..4aa4aaba6 100644 --- a/.github/workflows/reusable_python_example.yml +++ b/.github/workflows/reusable_python_example.yml @@ -22,7 +22,7 @@ jobs: run_notebook_test: name: test - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 defaults: run: shell: bash @@ -33,10 +33,10 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: '3.11.x' + python-version: '3.12.x' - name: Install Poetry - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true @@ -49,7 +49,8 @@ jobs: - name: Execute Python Example env: ENV: 'dev' + PYTHONFAULTHANDLER: 1 LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }} GRAPHISTRY_USERNAME: ${{ secrets.GRAPHISTRY_USERNAME }} GRAPHISTRY_PASSWORD: ${{ secrets.GRAPHISTRY_PASSWORD }} - run: poetry run python ${{ inputs.example-location }} \ No newline at end of file + run: poetry run python ${{ inputs.example-location }} diff --git a/.github/workflows/ruff_format.yaml b/.github/workflows/ruff_format.yaml index 959b7fc4b..a75a795e7 100644 --- a/.github/workflows/ruff_format.yaml +++ b/.github/workflows/ruff_format.yaml @@ -3,7 +3,7 @@ on: [ pull_request ] jobs: ruff: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: astral-sh/ruff-action@v2 diff --git a/.github/workflows/ruff_lint.yaml b/.github/workflows/ruff_lint.yaml index 214e8ec6d..4c4fb81e3 100644 --- a/.github/workflows/ruff_lint.yaml +++ b/.github/workflows/ruff_lint.yaml @@ -3,7 +3,7 @@ on: [ pull_request ] jobs: ruff: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: astral-sh/ruff-action@v2 diff --git a/.github/workflows/test_deduplication.yml b/.github/workflows/test_deduplication.yml index a1aab3252..923bbb68c 100644 --- a/.github/workflows/test_deduplication.yml +++ b/.github/workflows/test_deduplication.yml @@ -16,8 +16,7 @@ env: jobs: run_deduplication_test: name: test - runs-on: ubuntu-latest - if: ${{ github.event.label.name == 'run-checks' }} + runs-on: ubuntu-22.04 defaults: run: shell: bash @@ -46,7 +45,7 @@ jobs: python-version: '3.11.x' - name: Install Poetry - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true diff --git a/.github/workflows/test_milvus.yml b/.github/workflows/test_milvus.yml index c4214fddf..51e5f0982 100644 --- a/.github/workflows/test_milvus.yml +++ b/.github/workflows/test_milvus.yml @@ -17,8 +17,7 @@ jobs: run_milvus: name: test - runs-on: ubuntu-latest - if: ${{ github.event.label.name == 'run-checks' }} + runs-on: ubuntu-22.04 strategy: fail-fast: false defaults: @@ -36,7 +35,7 @@ jobs: - name: Install Poetry # https://github.com/snok/install-poetry#running-on-windows - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true diff --git a/.github/workflows/test_neo4j.yml b/.github/workflows/test_neo4j.yml index 5f955eefc..e1d71dcfd 100644 --- a/.github/workflows/test_neo4j.yml +++ b/.github/workflows/test_neo4j.yml @@ -15,8 +15,7 @@ env: jobs: run_neo4j_integration_test: name: test - if: ${{ github.event.label.name == 'run-checks' }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 defaults: run: @@ -32,7 +31,7 @@ jobs: python-version: '3.11.x' - name: Install Poetry - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true diff --git a/.github/workflows/test_pgvector.yml b/.github/workflows/test_pgvector.yml index 9f5ffd1da..d5356d603 100644 --- a/.github/workflows/test_pgvector.yml +++ b/.github/workflows/test_pgvector.yml @@ -17,8 +17,7 @@ jobs: run_pgvector_integration_test: name: test - runs-on: ubuntu-latest - if: ${{ github.event.label.name == 'run-checks' }} + runs-on: ubuntu-22.04 defaults: run: shell: bash @@ -47,7 +46,7 @@ jobs: python-version: '3.11.x' - name: Install Poetry - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true diff --git a/.github/workflows/test_python_3_10.yml b/.github/workflows/test_python_3_10.yml index 770d2fd63..2cf620a17 100644 --- a/.github/workflows/test_python_3_10.yml +++ b/.github/workflows/test_python_3_10.yml @@ -14,11 +14,9 @@ env: ENV: 'dev' jobs: - run_common: name: test - runs-on: ubuntu-latest - if: ${{ github.event.label.name == 'run-checks' }} + runs-on: ubuntu-22.04 strategy: fail-fast: false defaults: @@ -36,7 +34,7 @@ jobs: - name: Install Poetry # https://github.com/snok/install-poetry#running-on-windows - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true diff --git a/.github/workflows/test_python_3_11.yml b/.github/workflows/test_python_3_11.yml index 69eb875bd..b119dbcb2 100644 --- a/.github/workflows/test_python_3_11.yml +++ b/.github/workflows/test_python_3_11.yml @@ -17,8 +17,7 @@ jobs: run_common: name: test - runs-on: ubuntu-latest - if: ${{ github.event.label.name == 'run-checks' }} + runs-on: ubuntu-22.04 strategy: fail-fast: false defaults: @@ -36,7 +35,7 @@ jobs: - name: Install Poetry # https://github.com/snok/install-poetry#running-on-windows - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true diff --git a/.github/workflows/test_python_3_9.yml b/.github/workflows/test_python_3_12.yml similarity index 89% rename from .github/workflows/test_python_3_9.yml rename to .github/workflows/test_python_3_12.yml index 380c894ca..5a032144a 100644 --- a/.github/workflows/test_python_3_9.yml +++ b/.github/workflows/test_python_3_12.yml @@ -1,4 +1,4 @@ -name: test | python 3.9 +name: test | python 3.12 on: workflow_dispatch: @@ -17,8 +17,7 @@ jobs: run_common: name: test - runs-on: ubuntu-latest - if: ${{ github.event.label.name == 'run-checks' }} + runs-on: ubuntu-22.04 strategy: fail-fast: false defaults: @@ -32,11 +31,11 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: '3.9.x' + python-version: '3.12.x' - name: Install Poetry # https://github.com/snok/install-poetry#running-on-windows - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true diff --git a/.github/workflows/test_qdrant.yml b/.github/workflows/test_qdrant.yml index 17d9ac628..d1447c65c 100644 --- a/.github/workflows/test_qdrant.yml +++ b/.github/workflows/test_qdrant.yml @@ -17,9 +17,7 @@ jobs: run_qdrant_integration_test: name: test - runs-on: ubuntu-latest - if: ${{ github.event.label.name == 'run-checks' }} - + runs-on: ubuntu-22.04 defaults: run: shell: bash @@ -34,7 +32,7 @@ jobs: python-version: '3.11.x' - name: Install Poetry - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true diff --git a/.github/workflows/test_weaviate.yml b/.github/workflows/test_weaviate.yml index 9a3651dda..159fce194 100644 --- a/.github/workflows/test_weaviate.yml +++ b/.github/workflows/test_weaviate.yml @@ -17,9 +17,7 @@ jobs: run_weaviate_integration_test: name: test - runs-on: ubuntu-latest - if: ${{ github.event.label.name == 'run-checks' }} - + runs-on: ubuntu-22.04 defaults: run: shell: bash @@ -34,7 +32,7 @@ jobs: python-version: '3.11.x' - name: Install Poetry - uses: snok/install-poetry@v1.3.2 + uses: snok/install-poetry@v1.4.1 with: virtualenvs-create: true virtualenvs-in-project: true diff --git a/Dockerfile b/Dockerfile index cec626c86..50f2d0b08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ RUN pip install poetry RUN poetry config virtualenvs.create false # Install the dependencies -RUN poetry install --all-extras --no-root --no-dev +RUN poetry install --all-extras --no-root --without dev # Set the PYTHONPATH environment variable to include the /app directory diff --git a/alembic/env.py b/alembic/env.py index 1675e4168..60af9c0dd 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -4,8 +4,9 @@ from sqlalchemy import pool from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import async_engine_from_config - +from cognee.infrastructure.databases.relational import Base from alembic import context +from cognee.infrastructure.databases.relational import get_relational_engine, get_relational_config # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -20,7 +21,7 @@ # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata -from cognee.infrastructure.databases.relational import Base + target_metadata = Base.metadata # other values from the config, defined by the needs of env.py, @@ -83,12 +84,11 @@ def run_migrations_online() -> None: asyncio.run(run_async_migrations()) -from cognee.infrastructure.databases.relational import get_relational_engine, get_relational_config - db_engine = get_relational_engine() if db_engine.engine.dialect.name == "sqlite": from cognee.infrastructure.files.storage import LocalStorage + db_config = get_relational_config() LocalStorage.ensure_directory_exists(db_config.db_path) diff --git a/alembic/versions/482cd6517ce4_add_default_user.py b/alembic/versions/482cd6517ce4_add_default_user.py index b744c8821..92429e1e4 100644 --- a/alembic/versions/482cd6517ce4_add_default_user.py +++ b/alembic/versions/482cd6517ce4_add_default_user.py @@ -5,6 +5,7 @@ Create Date: 2024-10-16 22:17:18.634638 """ + from typing import Sequence, Union from sqlalchemy.util import await_only @@ -13,8 +14,8 @@ # revision identifiers, used by Alembic. -revision: str = '482cd6517ce4' -down_revision: Union[str, None] = '8057ae7329c2' +revision: str = "482cd6517ce4" +down_revision: Union[str, None] = "8057ae7329c2" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = "8057ae7329c2" diff --git a/alembic/versions/8057ae7329c2_initial_migration.py b/alembic/versions/8057ae7329c2_initial_migration.py index d7d45d0ac..48e795327 100644 --- a/alembic/versions/8057ae7329c2_initial_migration.py +++ b/alembic/versions/8057ae7329c2_initial_migration.py @@ -1,10 +1,11 @@ """Initial migration Revision ID: 8057ae7329c2 -Revises: +Revises: Create Date: 2024-10-02 12:55:20.989372 """ + from typing import Sequence, Union from sqlalchemy.util import await_only from cognee.infrastructure.databases.relational import get_relational_engine diff --git a/assets/Dashboard_example.png b/assets/Dashboard_example.png deleted file mode 100644 index 594186c58..000000000 Binary files a/assets/Dashboard_example.png and /dev/null differ diff --git a/assets/cognee-logo.png b/assets/cognee-logo.png deleted file mode 100644 index d02a831fb..000000000 Binary files a/assets/cognee-logo.png and /dev/null differ diff --git a/assets/cognee_logo.png b/assets/cognee_logo.png new file mode 100644 index 000000000..744ba2028 Binary files /dev/null and b/assets/cognee_logo.png differ diff --git a/assets/topoteretes_logo.png b/assets/topoteretes_logo.png deleted file mode 100644 index ebdbc9a20..000000000 Binary files a/assets/topoteretes_logo.png and /dev/null differ diff --git a/assets/vscode-debug-config.png b/assets/vscode-debug-config.png deleted file mode 100644 index 21f54400a..000000000 Binary files a/assets/vscode-debug-config.png and /dev/null differ diff --git a/cognee-mcp/.python-version b/cognee-mcp/.python-version new file mode 100644 index 000000000..9ac380418 --- /dev/null +++ b/cognee-mcp/.python-version @@ -0,0 +1 @@ +3.11.5 diff --git a/cognee-mcp/cognee_mcp/__init__.py b/cognee-mcp/cognee_mcp/__init__.py index e704e08dc..8c1ca4328 100644 --- a/cognee-mcp/cognee_mcp/__init__.py +++ b/cognee-mcp/cognee_mcp/__init__.py @@ -7,6 +7,7 @@ def main(): """Main entry point for the package.""" asyncio.run(server.main()) + # Optionally expose other important items at package level __all__ = ["main", "server"] diff --git a/cognee-mcp/cognee_mcp/server.py b/cognee-mcp/cognee_mcp/server.py index 739a86391..19dc999d7 100644 --- a/cognee-mcp/cognee_mcp/server.py +++ b/cognee-mcp/cognee_mcp/server.py @@ -18,7 +18,9 @@ def node_to_string(node): # keys_to_keep = ["chunk_index", "topological_rank", "cut_type", "id", "text"] # keyset = set(keys_to_keep) & node.keys() # return "Node(" + " ".join([key + ": " + str(node[key]) + "," for key in keyset]) + ")" - node_data = ", ".join([f"{key}: \"{value}\"" for key, value in node.items() if key in ["id", "name"]]) + node_data = ", ".join( + [f'{key}: "{value}"' for key, value in node.items() if key in ["id", "name"]] + ) return f"Node({node_data})" @@ -52,9 +54,9 @@ async def handle_list_tools() -> list[types.Tool]: """ return [ types.Tool( - name = "cognify", - description = "Build knowledge graph from the input text.", - inputSchema = { + name="cognify", + description="Build knowledge graph from the input text.", + inputSchema={ "type": "object", "properties": { "text": {"type": "string"}, @@ -65,9 +67,9 @@ async def handle_list_tools() -> list[types.Tool]: }, ), types.Tool( - name = "search", - description = "Search the knowledge graph.", - inputSchema = { + name="search", + description="Search the knowledge graph.", + inputSchema={ "type": "object", "properties": { "query": {"type": "string"}, @@ -76,9 +78,9 @@ async def handle_list_tools() -> list[types.Tool]: }, ), types.Tool( - name = "prune", - description = "Reset the knowledge graph.", - inputSchema = { + name="prune", + description="Reset the knowledge graph.", + inputSchema={ "type": "object", "properties": { "query": {"type": "string"}, @@ -90,8 +92,7 @@ async def handle_list_tools() -> list[types.Tool]: @server.call_tool() async def handle_call_tool( - name: str, - arguments: dict | None + name: str, arguments: dict | None ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: """ Handle tool execution requests. @@ -115,12 +116,12 @@ async def handle_call_tool( await cognee.add(text) - await cognee.cognify(graph_model = graph_model) + await cognee.cognify(graph_model=graph_model) return [ types.TextContent( - type = "text", - text = "Ingested", + type="text", + text="Ingested", ) ] elif name == "search": @@ -131,16 +132,14 @@ async def handle_call_tool( search_query = arguments.get("query") - search_results = await cognee.search( - SearchType.INSIGHTS, query_text = search_query - ) + search_results = await cognee.search(SearchType.INSIGHTS, query_text=search_query) results = retrieved_edges_to_string(search_results) return [ types.TextContent( - type = "text", - text = results, + type="text", + text=results, ) ] elif name == "prune": @@ -151,8 +150,8 @@ async def handle_call_tool( return [ types.TextContent( - type = "text", - text = "Pruned", + type="text", + text="Pruned", ) ] else: @@ -166,15 +165,16 @@ async def main(): read_stream, write_stream, InitializationOptions( - server_name = "cognee-mcp", - server_version = "0.1.0", - capabilities = server.get_capabilities( - notification_options = NotificationOptions(), - experimental_capabilities = {}, + server_name="cognee-mcp", + server_version="0.1.0", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, ), ), ) + # This is needed if you'd like to connect to a custom client if __name__ == "__main__": asyncio.run(main()) diff --git a/cognee-mcp/uv.lock b/cognee-mcp/uv.lock index 63dfae03b..b748667e1 100644 --- a/cognee-mcp/uv.lock +++ b/cognee-mcp/uv.lock @@ -321,6 +321,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, ] +[[package]] +name = "bokeh" +version = "3.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "jinja2" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pyyaml" }, + { name = "tornado" }, + { name = "xyzservices" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/9d/cc9c561e1db8cbecc5cfad972159020700fff2339bdaa316498ace1cb04c/bokeh-3.6.2.tar.gz", hash = "sha256:2f3043d9ecb3d5dc2e8c0ebf8ad55727617188d4e534f3e7208b36357e352396", size = 6247610 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/12/2c266a0dc57379c60b4e73a2f93e71343db4170bf26c5a76a74e7d8bce2a/bokeh-3.6.2-py3-none-any.whl", hash = "sha256:fddc4b91f8b40178c0e3e83dfcc33886d7803a3a1f041a840834255e435a18c2", size = 6866799 }, +] + [[package]] name = "boto3" version = "1.35.84" @@ -358,6 +378,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/07/14f8ad37f2d12a5ce41206c21820d8cb6561b728e51fad4530dff0552a67/cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", size = 9524 }, ] +[[package]] +name = "cairocffi" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/c5/1a4dc131459e68a173cbdab5fad6b524f53f9c1ef7861b7698e998b837cc/cairocffi-1.7.1.tar.gz", hash = "sha256:2e48ee864884ec4a3a34bfa8c9ab9999f688286eb714a15a43ec9d068c36557b", size = 88096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d8/ba13451aa6b745c49536e87b6bf8f629b950e84bd0e8308f7dc6883b67e2/cairocffi-1.7.1-py3-none-any.whl", hash = "sha256:9803a0e11f6c962f3b0ae2ec8ba6ae45e957a146a004697a1ac1bbf16b073b3f", size = 75611 }, +] + +[[package]] +name = "cairosvg" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cairocffi" }, + { name = "cssselect2" }, + { name = "defusedxml" }, + { name = "pillow" }, + { name = "tinycss2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/e6/ec5900b724e3c44af7f6f51f719919137284e5da4aabe96508baec8a1b40/CairoSVG-2.7.1.tar.gz", hash = "sha256:432531d72347291b9a9ebfb6777026b607563fd8719c46ee742db0aef7271ba0", size = 8399085 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/a5/1866b42151f50453f1a0d28fc4c39f5be5f412a2e914f33449c42daafdf1/CairoSVG-2.7.1-py3-none-any.whl", hash = "sha256:8a5222d4e6c3f86f1f7046b63246877a63b49923a1cd202184c3a634ef546b3b", size = 43235 }, +] + [[package]] name = "certifi" version = "2024.12.14" @@ -412,6 +460,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, ] +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + [[package]] name = "chardet" version = "5.2.0" @@ -480,7 +537,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -497,7 +554,9 @@ dependencies = [ { name = "aiosqlite" }, { name = "alembic" }, { name = "anthropic" }, + { name = "bokeh" }, { name = "boto3" }, + { name = "cairosvg" }, { name = "datasets" }, { name = "dlt", extra = ["sqlalchemy"] }, { name = "fastapi" }, @@ -518,6 +577,7 @@ dependencies = [ { name = "numpy" }, { name = "openai" }, { name = "pandas" }, + { name = "pre-commit" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "pypdf" }, @@ -541,8 +601,10 @@ requires-dist = [ { name = "alembic", specifier = ">=1.13.3,<2.0.0" }, { name = "anthropic", specifier = ">=0.26.1,<0.27.0" }, { name = "asyncpg", marker = "extra == 'postgres'", specifier = "==0.30.0" }, + { name = "bokeh", specifier = ">=3.6.2,<4.0.0" }, { name = "boto3", specifier = ">=1.26.125,<2.0.0" }, { name = "botocore", marker = "extra == 'filesystem'", specifier = ">=1.35.54,<2.0.0" }, + { name = "cairosvg", specifier = ">=2.7.1,<3.0.0" }, { name = "datasets", specifier = "==3.1.0" }, { name = "deepeval", marker = "extra == 'deepeval'", specifier = ">=2.0.1,<3.0.0" }, { name = "dlt", extras = ["sqlalchemy"], specifier = ">=1.4.1,<2.0.0" }, @@ -573,6 +635,7 @@ requires-dist = [ { name = "pandas", specifier = "==2.0.3" }, { name = "pgvector", marker = "extra == 'postgres'", specifier = ">=0.3.5,<0.4.0" }, { name = "posthog", marker = "extra == 'posthog'", specifier = ">=3.5.0,<4.0.0" }, + { name = "pre-commit", specifier = ">=4.0.1,<5.0.0" }, { name = "psycopg2", marker = "extra == 'postgres'", specifier = ">=2.9.10,<3.0.0" }, { name = "pydantic", specifier = "==2.8.2" }, { name = "pydantic-settings", specifier = ">=2.2.1,<3.0.0" }, @@ -885,6 +948,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/97/9b/443270b9210f13f6ef240eff73fd32e02d381e7103969dc66ce8e89ee901/cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", size = 3202071 }, ] +[[package]] +name = "cssselect2" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tinycss2" }, + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/fc/326cb6f988905998f09bb54a3f5d98d4462ba119363c0dfad29750d48c09/cssselect2-0.7.0.tar.gz", hash = "sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a", size = 35888 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/3a/e39436efe51894243ff145a37c4f9a030839b97779ebcc4f13b3ba21c54e/cssselect2-0.7.0-py3-none-any.whl", hash = "sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969", size = 15586 }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -996,6 +1072,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/60/533ce66e28295e2b94267126a851ac091ad29a835a9827d1f9c30574fce4/deepeval-2.0.6-py3-none-any.whl", hash = "sha256:57302830ff9d3d16ad4f1961338c7b4453e48039ff131990f258880728f33b6b", size = 504101 }, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + [[package]] name = "deprecated" version = "1.2.15" @@ -1056,6 +1141,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/69/1bcf70f81de1b4a9f21b3a62ec0c83bdff991c88d6cc2267d02408457e88/dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53", size = 25197 }, ] +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + [[package]] name = "distro" version = "1.9.0" @@ -1686,6 +1780,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/de/85a784bcc4a3779d1753a7ec2dee5de90e18c7bcf402e71b51fcf150b129/hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15", size = 12389 }, ] +[[package]] +name = "identify" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/5f/05f0d167be94585d502b4adf8c7af31f1dc0b1c7e14f9938a88fdbbcf4a7/identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02", size = 99179 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/f5/09644a3ad803fae9eca8efa17e1f2aef380c7f0b02f7ec4e8d446e51d64a/identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd", size = 99049 }, +] + [[package]] name = "idna" version = "3.10" @@ -2557,6 +2660,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442 }, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + [[package]] name = "numpy" version = "1.26.4" @@ -2946,7 +3058,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -2969,6 +3081,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d3/f2/5ee24cd69e2120bf87356c02ace0438b4e4fb78229fddcbf6f1c6be377d5/posthog-3.7.4-py2.py3-none-any.whl", hash = "sha256:21c18c6bf43b2de303ea4cd6e95804cc0f24c20cb2a96a8fd09da2ed50b62faa", size = 54777 }, ] +[[package]] +name = "pre-commit" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, +] + [[package]] name = "propcache" version = "0.2.1" @@ -3065,6 +3193,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/39/5a9a229bb5414abeb86e33b8fc8143ab0aecce5a7f698a53e31367d30caa/psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4", size = 1163736 }, { url = "https://files.pythonhosted.org/packages/3d/16/4623fad6076448df21c1a870c93a9774ad8a7b4dd1660223b59082dd8fec/psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067", size = 1025113 }, { url = "https://files.pythonhosted.org/packages/66/de/baed128ae0fc07460d9399d82e631ea31a1f171c0c4ae18f9808ac6759e3/psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e", size = 1163951 }, + { url = "https://files.pythonhosted.org/packages/ae/49/a6cfc94a9c483b1fa401fbcb23aca7892f60c7269c5ffa2ac408364f80dc/psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2", size = 2569060 }, ] [[package]] @@ -4223,6 +4352,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/75/c4d8b2f0fe7dac22854d88a9c509d428e78ac4bf284bc54cfe83f75cc13b/time_machine-2.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:4d3843143c46dddca6491a954bbd0abfd435681512ac343169560e9bab504129", size = 18047 }, ] +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, +] + [[package]] name = "tokenizers" version = "0.21.0" @@ -4257,12 +4398,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, ] +[[package]] +name = "tornado" +version = "6.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299 }, + { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253 }, + { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602 }, + { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972 }, + { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173 }, + { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892 }, + { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334 }, + { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261 }, + { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463 }, + { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907 }, +] + [[package]] name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [ @@ -4536,6 +4695,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/22/91b4bd36df27e651daedd93d03d5d3bb6029fdb0b55494e45ee46c36c570/validators-0.33.0-py3-none-any.whl", hash = "sha256:134b586a98894f8139865953899fc2daeb3d0c35569552c5518f089ae43ed075", size = 43298 }, ] +[[package]] +name = "virtualenv" +version = "20.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/75/53316a5a8050069228a2f6d11f32046cfa94fbb6cc3f08703f59b873de2e/virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa", size = 7650368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/f9/0919cf6f1432a8c4baa62511f8f8da8225432d22e83e3476f5be1a1edc6e/virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0", size = 4276702 }, +] + [[package]] name = "weaviate-client" version = "4.6.7" @@ -4692,6 +4865,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 }, ] +[[package]] +name = "xyzservices" +version = "2024.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/16/ae87cbd2d6bfc40a419077521c35aadf5121725b7bee3d7c51b56f50958b/xyzservices-2024.9.0.tar.gz", hash = "sha256:68fb8353c9dbba4f1ff6c0f2e5e4e596bb9e1db7f94f4f7dfbcb26e25aa66fde", size = 1131900 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/d3/e07ce413d16ef64e885bea37551eac4c5ca3ddd440933f9c94594273d0d9/xyzservices-2024.9.0-py3-none-any.whl", hash = "sha256:776ae82b78d6e5ca63dd6a94abb054df8130887a4a308473b54a6bd364de8644", size = 85130 }, +] + [[package]] name = "yarl" version = "1.18.3" diff --git a/cognee/__init__.py b/cognee/__init__.py index 591140b87..4ffd39577 100644 --- a/cognee/__init__.py +++ b/cognee/__init__.py @@ -4,11 +4,15 @@ from .api.v1.datasets.datasets import datasets from .api.v1.prune import prune from .api.v1.search import SearchType, get_search_history, search +from .api.v1.visualize import visualize +from .shared.utils import create_cognee_style_network_with_logo + # Pipelines from .modules import pipelines try: import dotenv + dotenv.load_dotenv() except ImportError: pass diff --git a/cognee/api/DTO.py b/cognee/api/DTO.py index 6bdfb4f82..fc2e1cc5f 100644 --- a/cognee/api/DTO.py +++ b/cognee/api/DTO.py @@ -4,12 +4,13 @@ class OutDTO(BaseModel): model_config = ConfigDict( - alias_generator = to_camel, - populate_by_name = True, + alias_generator=to_camel, + populate_by_name=True, ) + class InDTO(BaseModel): model_config = ConfigDict( - alias_generator = to_camel, - populate_by_name = True, + alias_generator=to_camel, + populate_by_name=True, ) diff --git a/cognee/api/client.py b/cognee/api/client.py index 95a273351..227ada304 100644 --- a/cognee/api/client.py +++ b/cognee/api/client.py @@ -1,4 +1,5 @@ -""" FastAPI server for the Cognee API. """ +"""FastAPI server for the Cognee API.""" + import os import uvicorn import logging @@ -6,9 +7,26 @@ from fastapi import FastAPI, status from fastapi.responses import JSONResponse, Response from fastapi.middleware.cors import CORSMiddleware - +from cognee.api.v1.permissions.routers import get_permissions_router +from cognee.api.v1.settings.routers import get_settings_router +from cognee.api.v1.datasets.routers import get_datasets_router +from cognee.api.v1.cognify.routers import get_cognify_router +from cognee.api.v1.search.routers import get_search_router +from cognee.api.v1.add.routers import get_add_router +from fastapi import Request +from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import RequestValidationError from cognee.exceptions import CogneeApiError from traceback import format_exc +from cognee.api.v1.users.routers import ( + get_auth_router, + get_register_router, + get_reset_password_router, + get_verify_router, + get_users_router, + get_visualize_router, +) +from contextlib import asynccontextmanager # Set up logging logging.basicConfig( @@ -19,15 +37,15 @@ if os.getenv("ENV", "prod") == "prod": sentry_sdk.init( - dsn = os.getenv("SENTRY_REPORTING_URL"), - traces_sample_rate = 1.0, - profiles_sample_rate = 1.0, + dsn=os.getenv("SENTRY_REPORTING_URL"), + traces_sample_rate=1.0, + profiles_sample_rate=1.0, ) -from contextlib import asynccontextmanager app_environment = os.getenv("ENV", "prod") + @asynccontextmanager async def lifespan(app: FastAPI): # from cognee.modules.data.deletion import prune_system, prune_data @@ -35,50 +53,42 @@ async def lifespan(app: FastAPI): # await prune_system(metadata = True) # if app_environment == "local" or app_environment == "dev": from cognee.infrastructure.databases.relational import get_relational_engine + db_engine = get_relational_engine() await db_engine.create_database() from cognee.modules.users.methods import get_default_user + await get_default_user() yield -app = FastAPI(debug = app_environment != "prod", lifespan = lifespan) + +app = FastAPI(debug=app_environment != "prod", lifespan=lifespan) app.add_middleware( CORSMiddleware, - allow_origins = ["*"], - allow_credentials = True, - allow_methods = ["OPTIONS", "GET", "POST", "DELETE"], - allow_headers = ["*"], + allow_origins=["*"], + allow_credentials=True, + allow_methods=["OPTIONS", "GET", "POST", "DELETE"], + allow_headers=["*"], ) -from cognee.api.v1.users.routers import get_auth_router, get_register_router,\ - get_reset_password_router, get_verify_router, get_users_router -from cognee.api.v1.permissions.routers import get_permissions_router -from cognee.api.v1.settings.routers import get_settings_router -from cognee.api.v1.datasets.routers import get_datasets_router -from cognee.api.v1.cognify.routers import get_cognify_router -from cognee.api.v1.search.routers import get_search_router -from cognee.api.v1.add.routers import get_add_router - -from fastapi import Request -from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import RequestValidationError @app.exception_handler(RequestValidationError) async def request_validation_exception_handler(request: Request, exc: RequestValidationError): if request.url.path == "/api/v1/auth/login": return JSONResponse( - status_code = 400, - content = {"detail": "LOGIN_BAD_CREDENTIALS"}, + status_code=400, + content={"detail": "LOGIN_BAD_CREDENTIALS"}, ) return JSONResponse( - status_code = 400, - content = jsonable_encoder({"detail": exc.errors(), "body": exc.body}), + status_code=400, + content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}), ) + @app.exception_handler(CogneeApiError) async def exception_handler(_: Request, exc: CogneeApiError) -> JSONResponse: detail = {} @@ -95,46 +105,42 @@ async def exception_handler(_: Request, exc: CogneeApiError) -> JSONResponse: # log the stack trace for easier serverside debugging logger.error(format_exc()) - return JSONResponse( - status_code=status_code, content={"detail": detail["message"]} - ) + return JSONResponse(status_code=status_code, content={"detail": detail["message"]}) -app.include_router( - get_auth_router(), - prefix = "/api/v1/auth", - tags = ["auth"] -) + +app.include_router(get_auth_router(), prefix="/api/v1/auth", tags=["auth"]) app.include_router( get_register_router(), - prefix = "/api/v1/auth", - tags = ["auth"], + prefix="/api/v1/auth", + tags=["auth"], ) app.include_router( get_reset_password_router(), - prefix = "/api/v1/auth", - tags = ["auth"], + prefix="/api/v1/auth", + tags=["auth"], ) app.include_router( get_verify_router(), - prefix = "/api/v1/auth", - tags = ["auth"], + prefix="/api/v1/auth", + tags=["auth"], ) app.include_router( get_users_router(), - prefix = "/api/v1/users", - tags = ["users"], + prefix="/api/v1/users", + tags=["users"], ) app.include_router( get_permissions_router(), - prefix = "/api/v1/permissions", - tags = ["permissions"], + prefix="/api/v1/permissions", + tags=["permissions"], ) + @app.get("/") async def root(): """ @@ -148,37 +154,21 @@ def health_check(): """ Health check endpoint that returns the server status. """ - return Response(status_code = 200) + return Response(status_code=200) -app.include_router( - get_datasets_router(), - prefix="/api/v1/datasets", - tags=["datasets"] -) -app.include_router( - get_add_router(), - prefix="/api/v1/add", - tags=["add"] -) +app.include_router(get_datasets_router(), prefix="/api/v1/datasets", tags=["datasets"]) -app.include_router( - get_cognify_router(), - prefix="/api/v1/cognify", - tags=["cognify"] -) +app.include_router(get_add_router(), prefix="/api/v1/add", tags=["add"]) -app.include_router( - get_search_router(), - prefix="/api/v1/search", - tags=["search"] -) +app.include_router(get_cognify_router(), prefix="/api/v1/cognify", tags=["cognify"]) + +app.include_router(get_search_router(), prefix="/api/v1/search", tags=["search"]) + +app.include_router(get_settings_router(), prefix="/api/v1/settings", tags=["settings"]) + +app.include_router(get_visualize_router(), prefix="/api/v1/visualize", tags=["visualize"]) -app.include_router( - get_settings_router(), - prefix="/api/v1/settings", - tags=["settings"] -) def start_api_server(host: str = "0.0.0.0", port: int = 8000): """ @@ -190,7 +180,7 @@ def start_api_server(host: str = "0.0.0.0", port: int = 8000): try: logger.info("Starting server at %s:%s", host, port) - uvicorn.run(app, host = host, port = port) + uvicorn.run(app, host=host, port=port) except Exception as e: logger.exception(f"Failed to start server: {e}") # Here you could add any cleanup code or error recovery code. diff --git a/cognee/api/v1/add/add.py b/cognee/api/v1/add/add.py index 39ee01964..c9f531269 100644 --- a/cognee/api/v1/add/add.py +++ b/cognee/api/v1/add/add.py @@ -14,10 +14,19 @@ from cognee.modules.users.permissions.methods import give_permission_on_document from cognee.modules.users.models import User from cognee.modules.data.methods import create_dataset -from cognee.infrastructure.databases.relational import create_db_and_tables as create_relational_db_and_tables -from cognee.infrastructure.databases.vector.pgvector import create_db_and_tables as create_pgvector_db_and_tables - -async def add(data: Union[BinaryIO, List[BinaryIO], str, List[str]], dataset_name: str = "main_dataset", user: User = None): +from cognee.infrastructure.databases.relational import ( + create_db_and_tables as create_relational_db_and_tables, +) +from cognee.infrastructure.databases.vector.pgvector import ( + create_db_and_tables as create_pgvector_db_and_tables, +) + + +async def add( + data: Union[BinaryIO, List[BinaryIO], str, List[str]], + dataset_name: str = "main_dataset", + user: User = None, +): await create_relational_db_and_tables() await create_pgvector_db_and_tables() @@ -25,7 +34,9 @@ async def add(data: Union[BinaryIO, List[BinaryIO], str, List[str]], dataset_nam if "data://" in data: # data is a data directory path datasets = get_matched_datasets(data.replace("data://", ""), dataset_name) - return await asyncio.gather(*[add(file_paths, dataset_name) for [dataset_name, file_paths] in datasets]) + return await asyncio.gather( + *[add(file_paths, dataset_name) for [dataset_name, file_paths] in datasets] + ) if "file://" in data: # data is a file path @@ -37,7 +48,7 @@ async def add(data: Union[BinaryIO, List[BinaryIO], str, List[str]], dataset_nam return await add([file_path], dataset_name) if hasattr(data, "file"): - file_path = save_data_to_file(data.file, filename = data.filename) + file_path = save_data_to_file(data.file, filename=data.filename) return await add([file_path], dataset_name) # data is a list of file paths or texts @@ -45,7 +56,7 @@ async def add(data: Union[BinaryIO, List[BinaryIO], str, List[str]], dataset_nam for data_item in data: if hasattr(data_item, "file"): - file_paths.append(save_data_to_file(data_item, filename = data_item.filename)) + file_paths.append(save_data_to_file(data_item, filename=data_item.filename)) elif isinstance(data_item, str) and ( data_item.startswith("/") or data_item.startswith("file://") ): @@ -58,10 +69,11 @@ async def add(data: Union[BinaryIO, List[BinaryIO], str, List[str]], dataset_nam return [] + async def add_files(file_paths: List[str], dataset_name: str, user: User = None): if user is None: user = await get_default_user() - + base_config = get_base_config() data_directory_path = base_config.data_root_directory @@ -72,7 +84,11 @@ async def add_files(file_paths: List[str], dataset_name: str, user: User = None) if data_directory_path not in file_path: file_name = file_path.split("/")[-1] - file_directory_path = data_directory_path + "/" + (dataset_name.replace(".", "/") + "/" if dataset_name != "main_dataset" else "") + file_directory_path = ( + data_directory_path + + "/" + + (dataset_name.replace(".", "/") + "/" if dataset_name != "main_dataset" else "") + ) dataset_file_path = path.join(file_directory_path, file_name) LocalStorage.ensure_directory_exists(file_directory_path) @@ -85,16 +101,20 @@ async def add_files(file_paths: List[str], dataset_name: str, user: User = None) destination = get_dlt_destination() pipeline = dlt.pipeline( - pipeline_name = "file_load_from_filesystem", - destination = destination, + pipeline_name="file_load_from_filesystem", + destination=destination, ) - dataset_name = dataset_name.replace(" ", "_").replace(".", "_") if dataset_name is not None else "main_dataset" + dataset_name = ( + dataset_name.replace(" ", "_").replace(".", "_") + if dataset_name is not None + else "main_dataset" + ) - @dlt.resource(standalone = True, merge_key = "id") + @dlt.resource(standalone=True, merge_key="id") async def data_resources(file_paths: str, user: User): for file_path in file_paths: - with open(file_path.replace("file://", ""), mode = "rb") as file: + with open(file_path.replace("file://", ""), mode="rb") as file: classified_data = ingestion.classify(file) data_id = ingestion.identify(classified_data) @@ -109,9 +129,9 @@ async def data_resources(file_paths: str, user: User): async with db_engine.get_async_session() as session: dataset = await create_dataset(dataset_name, user.id, session) - data = (await session.execute( - select(Data).filter(Data.id == data_id) - )).scalar_one_or_none() + data = ( + await session.execute(select(Data).filter(Data.id == data_id)) + ).scalar_one_or_none() if data is not None: data.name = file_metadata["name"] @@ -123,11 +143,11 @@ async def data_resources(file_paths: str, user: User): await session.commit() else: data = Data( - id = data_id, - name = file_metadata["name"], - raw_data_location = file_metadata["file_path"], - extension = file_metadata["extension"], - mime_type = file_metadata["mime_type"], + id=data_id, + name=file_metadata["name"], + raw_data_location=file_metadata["file_path"], + extension=file_metadata["extension"], + mime_type=file_metadata["mime_type"], ) dataset.data.append(data) @@ -144,14 +164,13 @@ async def data_resources(file_paths: str, user: User): await give_permission_on_document(user, data_id, "read") await give_permission_on_document(user, data_id, "write") - - send_telemetry("cognee.add EXECUTION STARTED", user_id = user.id) + send_telemetry("cognee.add EXECUTION STARTED", user_id=user.id) run_info = pipeline.run( data_resources(processed_file_paths, user), - table_name = "file_metadata", - dataset_name = dataset_name, - write_disposition = "merge", + table_name="file_metadata", + dataset_name=dataset_name, + write_disposition="merge", ) - send_telemetry("cognee.add EXECUTION COMPLETED", user_id = user.id) + send_telemetry("cognee.add EXECUTION COMPLETED", user_id=user.id) return run_info diff --git a/cognee/api/v1/add/add_v2.py b/cognee/api/v1/add/add_v2.py index 637c4a187..1ec30e67b 100644 --- a/cognee/api/v1/add/add_v2.py +++ b/cognee/api/v1/add/add_v2.py @@ -3,22 +3,28 @@ from cognee.modules.users.methods import get_default_user from cognee.modules.pipelines import run_tasks, Task from cognee.tasks.ingestion import ingest_data_with_metadata, resolve_data_directories -from cognee.infrastructure.databases.relational import create_db_and_tables as create_relational_db_and_tables -from cognee.infrastructure.databases.vector.pgvector import create_db_and_tables as create_pgvector_db_and_tables +from cognee.infrastructure.databases.relational import ( + create_db_and_tables as create_relational_db_and_tables, +) +from cognee.infrastructure.databases.vector.pgvector import ( + create_db_and_tables as create_pgvector_db_and_tables, +) -async def add(data: Union[BinaryIO, list[BinaryIO], str, list[str]], dataset_name: str = "main_dataset", user: User = None): + +async def add( + data: Union[BinaryIO, list[BinaryIO], str, list[str]], + dataset_name: str = "main_dataset", + user: User = None, +): await create_relational_db_and_tables() await create_pgvector_db_and_tables() if user is None: user = await get_default_user() - tasks = [ - Task(resolve_data_directories), - Task(ingest_data_with_metadata, dataset_name, user) - ] + tasks = [Task(resolve_data_directories), Task(ingest_data_with_metadata, dataset_name, user)] pipeline = run_tasks(tasks, data, "add_pipeline") async for result in pipeline: - print(result) \ No newline at end of file + print(result) diff --git a/cognee/api/v1/add/routers/__init__.py b/cognee/api/v1/add/routers/__init__.py index eebb250ab..bb820c033 100644 --- a/cognee/api/v1/add/routers/__init__.py +++ b/cognee/api/v1/add/routers/__init__.py @@ -1 +1 @@ -from .get_add_router import get_add_router \ No newline at end of file +from .get_add_router import get_add_router diff --git a/cognee/api/v1/add/routers/get_add_router.py b/cognee/api/v1/add/routers/get_add_router.py index 1f45d0c95..414552499 100644 --- a/cognee/api/v1/add/routers/get_add_router.py +++ b/cognee/api/v1/add/routers/get_add_router.py @@ -11,17 +11,19 @@ logger = logging.getLogger(__name__) + def get_add_router() -> APIRouter: router = APIRouter() @router.post("/", response_model=None) async def add( - data: List[UploadFile], - datasetId: str = Form(...), - user: User = Depends(get_authenticated_user), + data: List[UploadFile], + datasetId: str = Form(...), + user: User = Depends(get_authenticated_user), ): - """ This endpoint is responsible for adding data to the graph.""" + """This endpoint is responsible for adding data to the graph.""" from cognee.api.v1.add import add as cognee_add + try: if isinstance(data, str) and data.startswith("http"): if "github" in data: @@ -52,9 +54,6 @@ async def add( user=user, ) except Exception as error: - return JSONResponse( - status_code=409, - content={"error": str(error)} - ) + return JSONResponse(status_code=409, content={"error": str(error)}) - return router \ No newline at end of file + return router diff --git a/cognee/api/v1/authenticate_user/authenticate_user.py b/cognee/api/v1/authenticate_user/authenticate_user.py index 6761c65b8..791fe0357 100644 --- a/cognee/api/v1/authenticate_user/authenticate_user.py +++ b/cognee/api/v1/authenticate_user/authenticate_user.py @@ -1,4 +1,6 @@ -from cognee.infrastructure.databases.relational.user_authentication.users import authenticate_user_method +from cognee.infrastructure.databases.relational.user_authentication.users import ( + authenticate_user_method, +) async def authenticate_user(email: str, password: str): @@ -11,6 +13,7 @@ async def authenticate_user(email: str, password: str): if __name__ == "__main__": import asyncio + # Define an example user example_email = "example@example.com" example_password = "securepassword123" diff --git a/cognee/api/v1/cognify/code_graph_pipeline.py b/cognee/api/v1/cognify/code_graph_pipeline.py index 3d31b4000..405cb0b40 100644 --- a/cognee/api/v1/cognify/code_graph_pipeline.py +++ b/cognee/api/v1/cognify/code_graph_pipeline.py @@ -3,31 +3,30 @@ from pathlib import Path from cognee.base_config import get_base_config -from cognee.infrastructure.databases.vector.embeddings import \ - get_embedding_engine +from cognee.infrastructure.databases.vector.embeddings import get_embedding_engine from cognee.modules.cognify.config import get_cognify_config from cognee.modules.pipelines import run_tasks from cognee.modules.pipelines.tasks.Task import Task from cognee.modules.users.methods import get_default_user from cognee.shared.data_models import KnowledgeGraph, MonitoringTool -from cognee.tasks.documents import (classify_documents, - extract_chunks_from_documents) +from cognee.tasks.documents import classify_documents, extract_chunks_from_documents from cognee.tasks.graph import extract_graph_from_data from cognee.tasks.ingestion import ingest_data_with_metadata -from cognee.tasks.repo_processor import (enrich_dependency_graph, - expand_dependency_graph, - get_data_list_for_user, - get_non_py_files, - get_repo_file_dependencies) -from cognee.tasks.repo_processor.get_source_code_chunks import \ - get_source_code_chunks +from cognee.tasks.repo_processor import ( + enrich_dependency_graph, + expand_dependency_graph, + get_data_list_for_user, + get_non_py_files, + get_repo_file_dependencies, +) +from cognee.tasks.repo_processor.get_source_code_chunks import get_source_code_chunks from cognee.tasks.storage import add_data_points +from cognee.tasks.summarization import summarize_code, summarize_text monitoring = get_base_config().monitoring_tool if monitoring == MonitoringTool.LANGFUSE: from langfuse.decorators import observe -from cognee.tasks.summarization import summarize_code, summarize_text logger = logging.getLogger("code_graph_pipeline") update_status_lock = asyncio.Lock() @@ -42,9 +41,13 @@ async def run_code_graph_pipeline(repo_path, include_docs=True): from cognee.infrastructure.databases.relational import create_db_and_tables file_path = Path(__file__).parent - data_directory_path = str(pathlib.Path(os.path.join(file_path, ".data_storage/code_graph")).resolve()) + data_directory_path = str( + pathlib.Path(os.path.join(file_path, ".data_storage/code_graph")).resolve() + ) cognee.config.data_root_directory(data_directory_path) - cognee_directory_path = str(pathlib.Path(os.path.join(file_path, ".cognee_system/code_graph")).resolve()) + cognee_directory_path = str( + pathlib.Path(os.path.join(file_path, ".cognee_system/code_graph")).resolve() + ) cognee.config.system_root_directory(cognee_directory_path) await cognee.prune.prune_data() @@ -60,7 +63,11 @@ async def run_code_graph_pipeline(repo_path, include_docs=True): Task(get_repo_file_dependencies), Task(enrich_dependency_graph), Task(expand_dependency_graph, task_config={"batch_size": 50}), - Task(get_source_code_chunks, embedding_model=embedding_engine.model, task_config={"batch_size": 50}), + Task( + get_source_code_chunks, + embedding_model=embedding_engine.model, + task_config={"batch_size": 50}, + ), Task(summarize_code, task_config={"batch_size": 50}), Task(add_data_points, task_config={"batch_size": 50}), ] @@ -72,17 +79,19 @@ async def run_code_graph_pipeline(repo_path, include_docs=True): Task(get_data_list_for_user, dataset_name="repo_docs", user=user), Task(classify_documents), Task(extract_chunks_from_documents), - Task(extract_graph_from_data, graph_model=KnowledgeGraph, task_config={"batch_size": 50}), + Task( + extract_graph_from_data, graph_model=KnowledgeGraph, task_config={"batch_size": 50} + ), Task( summarize_text, summarization_model=cognee_config.summarization_model, - task_config={"batch_size": 50} + task_config={"batch_size": 50}, ), ] - + if include_docs: async for result in run_tasks(non_code_tasks, repo_path): yield result async for result in run_tasks(tasks, repo_path, "cognify_code_pipeline"): - yield result \ No newline at end of file + yield result diff --git a/cognee/api/v1/cognify/cognify_v2.py b/cognee/api/v1/cognify/cognify_v2.py index 4e2db5a70..680c05828 100644 --- a/cognee/api/v1/cognify/cognify_v2.py +++ b/cognee/api/v1/cognify/cognify_v2.py @@ -10,18 +10,18 @@ from cognee.modules.data.models import Data, Dataset from cognee.modules.pipelines import run_tasks from cognee.modules.pipelines.models import PipelineRunStatus -from cognee.modules.pipelines.operations.get_pipeline_status import \ - get_pipeline_status -from cognee.modules.pipelines.operations.log_pipeline_status import \ - log_pipeline_status +from cognee.modules.pipelines.operations.get_pipeline_status import get_pipeline_status +from cognee.modules.pipelines.operations.log_pipeline_status import log_pipeline_status from cognee.modules.pipelines.tasks.Task import Task from cognee.modules.users.methods import get_default_user from cognee.modules.users.models import User from cognee.shared.data_models import KnowledgeGraph from cognee.shared.utils import send_telemetry -from cognee.tasks.documents import (check_permissions_on_documents, - classify_documents, - extract_chunks_from_documents) +from cognee.tasks.documents import ( + check_permissions_on_documents, + classify_documents, + extract_chunks_from_documents, +) from cognee.tasks.graph import extract_graph_from_data from cognee.tasks.storage import add_data_points from cognee.tasks.storage.index_graph_edges import index_graph_edges @@ -31,7 +31,12 @@ update_status_lock = asyncio.Lock() -async def cognify(datasets: Union[str, list[str]] = None, user: User = None, graph_model: BaseModel = KnowledgeGraph): + +async def cognify( + datasets: Union[str, list[str]] = None, + user: User = None, + graph_model: BaseModel = KnowledgeGraph, +): if user is None: user = await get_default_user() @@ -41,7 +46,7 @@ async def cognify(datasets: Union[str, list[str]] = None, user: User = None, gra # If no datasets are provided, cognify all existing datasets. datasets = existing_datasets - if type(datasets[0]) == str: + if isinstance(datasets[0], str): datasets = await get_datasets_by_name(datasets, user.id) existing_datasets_map = { @@ -59,8 +64,10 @@ async def cognify(datasets: Union[str, list[str]] = None, user: User = None, gra return await asyncio.gather(*awaitables) -async def run_cognify_pipeline(dataset: Dataset, user: User, graph_model: BaseModel = KnowledgeGraph): - data_documents: list[Data] = await get_dataset_data(dataset_id = dataset.id) +async def run_cognify_pipeline( + dataset: Dataset, user: User, graph_model: BaseModel = KnowledgeGraph +): + data_documents: list[Data] = await get_dataset_data(dataset_id=dataset.id) document_ids_str = [str(document.id) for document in data_documents] @@ -69,32 +76,41 @@ async def run_cognify_pipeline(dataset: Dataset, user: User, graph_model: BaseMo send_telemetry("cognee.cognify EXECUTION STARTED", user.id) - #async with update_status_lock: TODO: Add UI lock to prevent multiple backend requests + # async with update_status_lock: TODO: Add UI lock to prevent multiple backend requests task_status = await get_pipeline_status([dataset_id]) - if dataset_id in task_status and task_status[dataset_id] == PipelineRunStatus.DATASET_PROCESSING_STARTED: + if ( + dataset_id in task_status + and task_status[dataset_id] == PipelineRunStatus.DATASET_PROCESSING_STARTED + ): logger.info("Dataset %s is already being processed.", dataset_name) return - await log_pipeline_status(dataset_id, PipelineRunStatus.DATASET_PROCESSING_STARTED, { - "dataset_name": dataset_name, - "files": document_ids_str, - }) + await log_pipeline_status( + dataset_id, + PipelineRunStatus.DATASET_PROCESSING_STARTED, + { + "dataset_name": dataset_name, + "files": document_ids_str, + }, + ) try: cognee_config = get_cognify_config() tasks = [ Task(classify_documents), - Task(check_permissions_on_documents, user = user, permissions = ["write"]), - Task(extract_chunks_from_documents), # Extract text chunks based on the document type. - Task(extract_graph_from_data, graph_model = graph_model, task_config = { "batch_size": 10 }), # Generate knowledge graphs from the document chunks. + Task(check_permissions_on_documents, user=user, permissions=["write"]), + Task(extract_chunks_from_documents), # Extract text chunks based on the document type. + Task( + extract_graph_from_data, graph_model=graph_model, task_config={"batch_size": 10} + ), # Generate knowledge graphs from the document chunks. Task( summarize_text, - summarization_model = cognee_config.summarization_model, - task_config = { "batch_size": 10 } + summarization_model=cognee_config.summarization_model, + task_config={"batch_size": 10}, ), - Task(add_data_points, only_root = True, task_config = { "batch_size": 10 }), + Task(add_data_points, only_root=True, task_config={"batch_size": 10}), ] pipeline = run_tasks(tasks, data_documents, "cognify_pipeline") @@ -106,17 +122,25 @@ async def run_cognify_pipeline(dataset: Dataset, user: User, graph_model: BaseMo send_telemetry("cognee.cognify EXECUTION COMPLETED", user.id) - await log_pipeline_status(dataset_id, PipelineRunStatus.DATASET_PROCESSING_COMPLETED, { - "dataset_name": dataset_name, - "files": document_ids_str, - }) + await log_pipeline_status( + dataset_id, + PipelineRunStatus.DATASET_PROCESSING_COMPLETED, + { + "dataset_name": dataset_name, + "files": document_ids_str, + }, + ) except Exception as error: send_telemetry("cognee.cognify EXECUTION ERRORED", user.id) - await log_pipeline_status(dataset_id, PipelineRunStatus.DATASET_PROCESSING_ERRORED, { - "dataset_name": dataset_name, - "files": document_ids_str, - }) + await log_pipeline_status( + dataset_id, + PipelineRunStatus.DATASET_PROCESSING_ERRORED, + { + "dataset_name": dataset_name, + "files": document_ids_str, + }, + ) raise error diff --git a/cognee/api/v1/cognify/routers/__init__.py b/cognee/api/v1/cognify/routers/__init__.py index c6d52bfa2..6e5f9cc9d 100644 --- a/cognee/api/v1/cognify/routers/__init__.py +++ b/cognee/api/v1/cognify/routers/__init__.py @@ -1 +1 @@ -from .get_cognify_router import get_cognify_router \ No newline at end of file +from .get_cognify_router import get_cognify_router diff --git a/cognee/api/v1/cognify/routers/get_cognify_router.py b/cognee/api/v1/cognify/routers/get_cognify_router.py index 257ac994f..82ec4b857 100644 --- a/cognee/api/v1/cognify/routers/get_cognify_router.py +++ b/cognee/api/v1/cognify/routers/get_cognify_router.py @@ -7,23 +7,23 @@ from fastapi import Depends from cognee.shared.data_models import KnowledgeGraph + class CognifyPayloadDTO(BaseModel): datasets: List[str] graph_model: Optional[BaseModel] = KnowledgeGraph + def get_cognify_router() -> APIRouter: router = APIRouter() @router.post("/", response_model=None) async def cognify(payload: CognifyPayloadDTO, user: User = Depends(get_authenticated_user)): - """ This endpoint is responsible for the cognitive processing of the content.""" + """This endpoint is responsible for the cognitive processing of the content.""" from cognee.api.v1.cognify.cognify_v2 import cognify as cognee_cognify + try: await cognee_cognify(payload.datasets, user, payload.graph_model) except Exception as error: - return JSONResponse( - status_code=409, - content={"error": str(error)} - ) + return JSONResponse(status_code=409, content={"error": str(error)}) return router diff --git a/cognee/api/v1/config/config.py b/cognee/api/v1/config/config.py index 1347fcba8..da58cf581 100644 --- a/cognee/api/v1/config/config.py +++ b/cognee/api/v1/config/config.py @@ -1,4 +1,5 @@ -""" This module is used to set the configuration of the system.""" +"""This module is used to set the configuration of the system.""" + import os from cognee.base_config import get_base_config from cognee.exceptions import InvalidValueError, InvalidAttributeError @@ -10,7 +11,8 @@ from cognee.infrastructure.databases.relational import get_relational_config from cognee.infrastructure.files.storage import LocalStorage -class config(): + +class config: @staticmethod def system_root_directory(system_root_directory: str): databases_directory_path = os.path.join(system_root_directory, "databases") @@ -39,12 +41,12 @@ def monitoring_tool(monitoring_tool: object): @staticmethod def set_classification_model(classification_model: object): cognify_config = get_cognify_config() - cognify_config.classification_model = classification_model + cognify_config.classification_model = classification_model @staticmethod def set_summarization_model(summarization_model: object): cognify_config = get_cognify_config() - cognify_config.summarization_model=summarization_model + cognify_config.summarization_model = summarization_model @staticmethod def set_graph_model(graph_model: object): @@ -79,14 +81,16 @@ def set_llm_api_key(llm_api_key: str): @staticmethod def set_llm_config(config_dict: dict): """ - Updates the llm config with values from config_dict. + Updates the llm config with values from config_dict. """ llm_config = get_llm_config() for key, value in config_dict.items(): if hasattr(llm_config, key): object.__setattr__(llm_config, key, value) else: - raise InvalidAttributeError(message=f"'{key}' is not a valid attribute of the config.") + raise InvalidAttributeError( + message=f"'{key}' is not a valid attribute of the config." + ) @staticmethod def set_chunk_strategy(chunk_strategy: object): @@ -108,7 +112,6 @@ def set_chunk_size(chunk_size: object): chunk_config = get_chunk_config() chunk_config.chunk_size = chunk_size - @staticmethod def set_vector_db_provider(vector_db_provider: str): vector_db_config = get_vectordb_config() @@ -117,33 +120,36 @@ def set_vector_db_provider(vector_db_provider: str): @staticmethod def set_relational_db_config(config_dict: dict): """ - Updates the relational db config with values from config_dict. + Updates the relational db config with values from config_dict. """ relational_db_config = get_relational_config() for key, value in config_dict.items(): if hasattr(relational_db_config, key): object.__setattr__(relational_db_config, key, value) else: - raise InvalidAttributeError(message=f"'{key}' is not a valid attribute of the config.") + raise InvalidAttributeError( + message=f"'{key}' is not a valid attribute of the config." + ) @staticmethod def set_vector_db_config(config_dict: dict): """ - Updates the vector db config with values from config_dict. + Updates the vector db config with values from config_dict. """ vector_db_config = get_vectordb_config() for key, value in config_dict.items(): if hasattr(vector_db_config, key): object.__setattr__(vector_db_config, key, value) else: - raise InvalidAttributeError(message=f"'{key}' is not a valid attribute of the config.") + raise InvalidAttributeError( + message=f"'{key}' is not a valid attribute of the config." + ) @staticmethod def set_vector_db_key(db_key: str): vector_db_config = get_vectordb_config() vector_db_config.vector_db_key = db_key - @staticmethod def set_vector_db_url(db_url: str): vector_db_config = get_vectordb_config() @@ -154,7 +160,9 @@ def set_graphistry_config(graphistry_config: dict[str, str]): base_config = get_base_config() if "username" not in graphistry_config or "password" not in graphistry_config: - raise InvalidValueError(message="graphistry_config dictionary must contain 'username' and 'password' keys.") + raise InvalidValueError( + message="graphistry_config dictionary must contain 'username' and 'password' keys." + ) base_config.graphistry_username = graphistry_config.get("username") base_config.graphistry_password = graphistry_config.get("password") diff --git a/cognee/api/v1/datasets/datasets.py b/cognee/api/v1/datasets/datasets.py index 345f8be7b..a6552d937 100644 --- a/cognee/api/v1/datasets/datasets.py +++ b/cognee/api/v1/datasets/datasets.py @@ -1,10 +1,13 @@ from cognee.modules.users.methods import get_default_user from cognee.modules.ingestion import discover_directory_datasets from cognee.modules.pipelines.operations.get_pipeline_status import get_pipeline_status -class datasets(): + + +class datasets: @staticmethod async def list_datasets(): from cognee.modules.data.methods import get_datasets + user = await get_default_user() return await get_datasets(user.id) @@ -15,6 +18,7 @@ def discover_datasets(directory_path: str): @staticmethod async def list_data(dataset_id: str): from cognee.modules.data.methods import get_dataset, get_dataset_data + user = await get_default_user() dataset = await get_dataset(user.id, dataset_id) @@ -28,6 +32,7 @@ async def get_status(dataset_ids: list[str]) -> dict: @staticmethod async def delete_dataset(dataset_id: str): from cognee.modules.data.methods import get_dataset, delete_dataset + user = await get_default_user() dataset = await get_dataset(user.id, dataset_id) diff --git a/cognee/api/v1/datasets/routers/__init__.py b/cognee/api/v1/datasets/routers/__init__.py index f03428fd6..24783903d 100644 --- a/cognee/api/v1/datasets/routers/__init__.py +++ b/cognee/api/v1/datasets/routers/__init__.py @@ -1 +1 @@ -from .get_datasets_router import get_datasets_router \ No newline at end of file +from .get_datasets_router import get_datasets_router diff --git a/cognee/api/v1/datasets/routers/get_datasets_router.py b/cognee/api/v1/datasets/routers/get_datasets_router.py index 1ba96a232..ba65cd94c 100644 --- a/cognee/api/v1/datasets/routers/get_datasets_router.py +++ b/cognee/api/v1/datasets/routers/get_datasets_router.py @@ -16,9 +16,11 @@ logger = logging.getLogger(__name__) + class ErrorResponseDTO(BaseModel): message: str + class DatasetDTO(OutDTO): id: UUID name: str @@ -26,6 +28,7 @@ class DatasetDTO(OutDTO): updated_at: Optional[datetime] = None owner_id: UUID + class DataDTO(OutDTO): id: UUID name: str @@ -35,6 +38,7 @@ class DataDTO(OutDTO): mime_type: str raw_data_location: str + def get_datasets_router() -> APIRouter: router = APIRouter() @@ -42,46 +46,51 @@ def get_datasets_router() -> APIRouter: async def get_datasets(user: User = Depends(get_authenticated_user)): try: from cognee.modules.data.methods import get_datasets + datasets = await get_datasets(user.id) return datasets except Exception as error: logger.error(f"Error retrieving datasets: {str(error)}") - raise HTTPException(status_code=500, detail=f"Error retrieving datasets: {str(error)}") from error + raise HTTPException( + status_code=500, detail=f"Error retrieving datasets: {str(error)}" + ) from error - @router.delete("/{dataset_id}", response_model=None, responses={404: {"model": ErrorResponseDTO}}) + @router.delete( + "/{dataset_id}", response_model=None, responses={404: {"model": ErrorResponseDTO}} + ) async def delete_dataset(dataset_id: str, user: User = Depends(get_authenticated_user)): from cognee.modules.data.methods import get_dataset, delete_dataset dataset = await get_dataset(user.id, dataset_id) if dataset is None: - raise EntityNotFoundError( - message=f"Dataset ({dataset_id}) not found." - ) + raise EntityNotFoundError(message=f"Dataset ({dataset_id}) not found.") await delete_dataset(dataset) - @router.delete("/{dataset_id}/data/{data_id}", response_model=None, responses={404: {"model": ErrorResponseDTO}}) - async def delete_data(dataset_id: str, data_id: str, user: User = Depends(get_authenticated_user)): + @router.delete( + "/{dataset_id}/data/{data_id}", + response_model=None, + responses={404: {"model": ErrorResponseDTO}}, + ) + async def delete_data( + dataset_id: str, data_id: str, user: User = Depends(get_authenticated_user) + ): from cognee.modules.data.methods import get_data, delete_data from cognee.modules.data.methods import get_dataset # Check if user has permission to access dataset and data by trying to get the dataset dataset = await get_dataset(user.id, dataset_id) - #TODO: Handle situation differently if user doesn't have permission to access data? + # TODO: Handle situation differently if user doesn't have permission to access data? if dataset is None: - raise EntityNotFoundError( - message=f"Dataset ({dataset_id}) not found." - ) + raise EntityNotFoundError(message=f"Dataset ({dataset_id}) not found.") data = await get_data(user.id, data_id) if data is None: - raise EntityNotFoundError( - message=f"Data ({data_id}) not found." - ) + raise EntityNotFoundError(message=f"Data ({data_id}) not found.") await delete_data(data) @@ -98,14 +107,18 @@ async def get_dataset_graph(dataset_id: str, user: User = Depends(get_authentica status_code=200, content=str(graph_url), ) - except: + except Exception as error: + print(error) return JSONResponse( status_code=409, content="Graphistry credentials are not set. Please set them in your .env file.", ) - @router.get("/{dataset_id}/data", response_model=list[DataDTO], - responses={404: {"model": ErrorResponseDTO}}) + @router.get( + "/{dataset_id}/data", + response_model=list[DataDTO], + responses={404: {"model": ErrorResponseDTO}}, + ) async def get_dataset_data(dataset_id: str, user: User = Depends(get_authenticated_user)): from cognee.modules.data.methods import get_dataset_data, get_dataset @@ -125,8 +138,10 @@ async def get_dataset_data(dataset_id: str, user: User = Depends(get_authenticat return dataset_data @router.get("/status", response_model=dict[str, PipelineRunStatus]) - async def get_dataset_status(datasets: Annotated[List[str], Query(alias="dataset")] = None, - user: User = Depends(get_authenticated_user)): + async def get_dataset_status( + datasets: Annotated[List[str], Query(alias="dataset")] = None, + user: User = Depends(get_authenticated_user), + ): from cognee.api.v1.datasets.datasets import datasets as cognee_datasets try: @@ -134,13 +149,12 @@ async def get_dataset_status(datasets: Annotated[List[str], Query(alias="dataset return datasets_statuses except Exception as error: - return JSONResponse( - status_code=409, - content={"error": str(error)} - ) + return JSONResponse(status_code=409, content={"error": str(error)}) @router.get("/{dataset_id}/data/{data_id}/raw", response_class=FileResponse) - async def get_raw_data(dataset_id: str, data_id: str, user: User = Depends(get_authenticated_user)): + async def get_raw_data( + dataset_id: str, data_id: str, user: User = Depends(get_authenticated_user) + ): from cognee.modules.data.methods import get_data from cognee.modules.data.methods import get_dataset, get_dataset_data @@ -148,10 +162,7 @@ async def get_raw_data(dataset_id: str, data_id: str, user: User = Depends(get_a if dataset is None: return JSONResponse( - status_code=404, - content={ - "detail": f"Dataset ({dataset_id}) not found." - } + status_code=404, content={"detail": f"Dataset ({dataset_id}) not found."} ) dataset_data = await get_dataset_data(dataset.id) @@ -163,13 +174,17 @@ async def get_raw_data(dataset_id: str, data_id: str, user: User = Depends(get_a # Check if matching_data contains an element if len(matching_data) == 0: - raise EntityNotFoundError(message= f"Data ({data_id}) not found in dataset ({dataset_id}).") + raise EntityNotFoundError( + message=f"Data ({data_id}) not found in dataset ({dataset_id})." + ) data = await get_data(user.id, data_id) if data is None: - raise EntityNotFoundError(message=f"Data ({data_id}) not found in dataset ({dataset_id}).") + raise EntityNotFoundError( + message=f"Data ({data_id}) not found in dataset ({dataset_id})." + ) return data.raw_data_location - return router \ No newline at end of file + return router diff --git a/cognee/api/v1/permissions/routers/__init__.py b/cognee/api/v1/permissions/routers/__init__.py index 986b52c3e..be7e5bbc4 100644 --- a/cognee/api/v1/permissions/routers/__init__.py +++ b/cognee/api/v1/permissions/routers/__init__.py @@ -1 +1 @@ -from .get_permissions_router import get_permissions_router \ No newline at end of file +from .get_permissions_router import get_permissions_router diff --git a/cognee/api/v1/permissions/routers/get_permissions_router.py b/cognee/api/v1/permissions/routers/get_permissions_router.py index 2b30f62fd..78b822085 100644 --- a/cognee/api/v1/permissions/routers/get_permissions_router.py +++ b/cognee/api/v1/permissions/routers/get_permissions_router.py @@ -10,40 +10,56 @@ from cognee.modules.users import get_user_db from cognee.modules.users.models import User, Group, Permission, UserGroup, GroupPermission + def get_permissions_router() -> APIRouter: permissions_router = APIRouter() @permissions_router.post("/groups/{group_id}/permissions") - async def give_permission_to_group(group_id: str, permission: str, db: Session = Depends(get_user_db)): - group = (await db.session.execute(select(Group).where(Group.id == group_id))).scalars().first() + async def give_permission_to_group( + group_id: str, permission: str, db: Session = Depends(get_user_db) + ): + group = ( + (await db.session.execute(select(Group).where(Group.id == group_id))).scalars().first() + ) if not group: raise GroupNotFoundError permission_entity = ( - await db.session.execute(select(Permission).where(Permission.name == permission))).scalars().first() + (await db.session.execute(select(Permission).where(Permission.name == permission))) + .scalars() + .first() + ) if not permission_entity: stmt = insert(Permission).values(name=permission) await db.session.execute(stmt) permission_entity = ( - await db.session.execute(select(Permission).where(Permission.name == permission))).scalars().first() + (await db.session.execute(select(Permission).where(Permission.name == permission))) + .scalars() + .first() + ) try: # add permission to group await db.session.execute( - insert(GroupPermission).values(group_id=group.id, permission_id=permission_entity.id)) - except IntegrityError as e: + insert(GroupPermission).values( + group_id=group.id, permission_id=permission_entity.id + ) + ) + except IntegrityError: raise EntityAlreadyExistsError(message="Group permission already exists.") await db.session.commit() - return JSONResponse(status_code = 200, content = {"message": "Permission assigned to group"}) + return JSONResponse(status_code=200, content={"message": "Permission assigned to group"}) @permissions_router.post("/users/{user_id}/groups") async def add_user_to_group(user_id: str, group_id: str, db: Session = Depends(get_user_db)): user = (await db.session.execute(select(User).where(User.id == user_id))).scalars().first() - group = (await db.session.execute(select(Group).where(Group.id == group_id))).scalars().first() + group = ( + (await db.session.execute(select(Group).where(Group.id == group_id))).scalars().first() + ) if not user: raise UserNotFoundError @@ -54,11 +70,11 @@ async def add_user_to_group(user_id: str, group_id: str, db: Session = Depends(g # Add association directly to the association table stmt = insert(UserGroup).values(user_id=user_id, group_id=group_id) await db.session.execute(stmt) - except IntegrityError as e: + except IntegrityError: raise EntityAlreadyExistsError(message="User is already part of group.") await db.session.commit() - return JSONResponse(status_code = 200, content = {"message": "User added to group"}) + return JSONResponse(status_code=200, content={"message": "User added to group"}) return permissions_router diff --git a/cognee/api/v1/prune/prune.py b/cognee/api/v1/prune/prune.py index 4237ac58e..efee07e21 100644 --- a/cognee/api/v1/prune/prune.py +++ b/cognee/api/v1/prune/prune.py @@ -1,19 +1,21 @@ from cognee.modules.data.deletion import prune_system, prune_data -class prune(): + +class prune: @staticmethod async def prune_data(): await prune_data() @staticmethod - async def prune_system(graph = True, vector = True, metadata = False): + async def prune_system(graph=True, vector=True, metadata=False): await prune_system(graph, vector, metadata) + if __name__ == "__main__": import asyncio + async def main(): await prune.prune_data() await prune.prune_system() - asyncio.run(main()) diff --git a/cognee/api/v1/search/get_search_history.py b/cognee/api/v1/search/get_search_history.py index fada67c85..04ec49978 100644 --- a/cognee/api/v1/search/get_search_history.py +++ b/cognee/api/v1/search/get_search_history.py @@ -2,8 +2,9 @@ from cognee.modules.users.methods import get_default_user from cognee.modules.users.models import User + async def get_search_history(user: User = None) -> list: if not user: user = await get_default_user() - + return await get_history(user.id) diff --git a/cognee/api/v1/search/routers/__init__.py b/cognee/api/v1/search/routers/__init__.py index c3b199f5f..d30e865e2 100644 --- a/cognee/api/v1/search/routers/__init__.py +++ b/cognee/api/v1/search/routers/__init__.py @@ -1 +1 @@ -from .get_search_router import get_search_router \ No newline at end of file +from .get_search_router import get_search_router diff --git a/cognee/api/v1/search/routers/get_search_router.py b/cognee/api/v1/search/routers/get_search_router.py index 893067c20..a97e84cf4 100644 --- a/cognee/api/v1/search/routers/get_search_router.py +++ b/cognee/api/v1/search/routers/get_search_router.py @@ -13,6 +13,7 @@ class SearchPayloadDTO(InDTO): search_type: SearchType query: str + def get_search_router() -> APIRouter: router = APIRouter() @@ -22,21 +23,18 @@ class SearchHistoryItem(OutDTO): user: str created_at: datetime - @router.get("/", response_model = list[SearchHistoryItem]) + @router.get("/", response_model=list[SearchHistoryItem]) async def get_search_history(user: User = Depends(get_authenticated_user)): try: history = await get_history(user.id) return history except Exception as error: - return JSONResponse( - status_code = 500, - content = {"error": str(error)} - ) + return JSONResponse(status_code=500, content={"error": str(error)}) - @router.post("/", response_model = list) + @router.post("/", response_model=list) async def search(payload: SearchPayloadDTO, user: User = Depends(get_authenticated_user)): - """ This endpoint is responsible for searching for nodes in the graph.""" + """This endpoint is responsible for searching for nodes in the graph.""" from cognee.api.v1.search import search as cognee_search try: @@ -44,9 +42,6 @@ async def search(payload: SearchPayloadDTO, user: User = Depends(get_authenticat return results except Exception as error: - return JSONResponse( - status_code = 409, - content = {"error": str(error)} - ) + return JSONResponse(status_code=409, content={"error": str(error)}) return router diff --git a/cognee/api/v1/search/search.legacy.py b/cognee/api/v1/search/search.legacy.py index cea3b3874..c4e490f01 100644 --- a/cognee/api/v1/search/search.legacy.py +++ b/cognee/api/v1/search/search.legacy.py @@ -1,4 +1,5 @@ -""" This module contains the search function that is used to search for nodes in the graph.""" +"""This module contains the search function that is used to search for nodes in the graph.""" + import asyncio from enum import Enum from typing import Dict, Any, Callable, List @@ -16,6 +17,7 @@ from cognee.modules.users.methods import get_default_user from cognee.modules.users.models import User + class SearchType(Enum): ADJACENT = "ADJACENT" TRAVERSE = "TRAVERSE" @@ -23,7 +25,7 @@ class SearchType(Enum): SUMMARY = "SUMMARY" SUMMARY_CLASSIFICATION = "SUMMARY_CLASSIFICATION" NODE_CLASSIFICATION = "NODE_CLASSIFICATION" - DOCUMENT_CLASSIFICATION = "DOCUMENT_CLASSIFICATION", + DOCUMENT_CLASSIFICATION = ("DOCUMENT_CLASSIFICATION",) CYPHER = "CYPHER" @staticmethod @@ -33,12 +35,13 @@ def from_str(name: str): except KeyError as error: raise ValueError(f"{name} is not a valid SearchType") from error + class SearchParameters(BaseModel): search_type: SearchType params: Dict[str, Any] @field_validator("search_type", mode="before") - def convert_string_to_enum(cls, value): # pylint: disable=no-self-argument + def convert_string_to_enum(cls, value): # pylint: disable=no-self-argument if isinstance(value, str): return SearchType.from_str(value) return value @@ -52,7 +55,7 @@ async def search(search_type: str, params: Dict[str, Any], user: User = None) -> raise UserNotFoundError own_document_ids = await get_document_ids_for_user(user.id) - search_params = SearchParameters(search_type = search_type, params = params) + search_params = SearchParameters(search_type=search_type, params=params) search_results = await specific_search([search_params], user) from uuid import UUID @@ -61,7 +64,7 @@ async def search(search_type: str, params: Dict[str, Any], user: User = None) -> for search_result in search_results: document_id = search_result["document_id"] if "document_id" in search_result else None - document_id = UUID(document_id) if type(document_id) == str else document_id + document_id = UUID(document_id) if isinstance(document_id, str) else document_id if document_id is None or document_id in own_document_ids: filtered_search_results.append(search_result) diff --git a/cognee/api/v1/search/search_v2.py b/cognee/api/v1/search/search_v2.py index 222ec6791..52fc565c7 100644 --- a/cognee/api/v1/search/search_v2.py +++ b/cognee/api/v1/search/search_v2.py @@ -16,14 +16,20 @@ from cognee.tasks.summarization import query_summaries from cognee.tasks.completion import query_completion + class SearchType(Enum): SUMMARIES = "SUMMARIES" INSIGHTS = "INSIGHTS" CHUNKS = "CHUNKS" COMPLETION = "COMPLETION" -async def search(query_type: SearchType, query_text: str, user: User = None, - datasets: Union[list[str], str, None] = None) -> list: + +async def search( + query_type: SearchType, + query_text: str, + user: User = None, + datasets: Union[list[str], str, None] = None, +) -> list: # We use lists from now on for datasets if isinstance(datasets, str): datasets = [datasets] @@ -43,15 +49,16 @@ async def search(query_type: SearchType, query_text: str, user: User = None, for search_result in search_results: document_id = search_result["document_id"] if "document_id" in search_result else None - document_id = UUID(document_id) if type(document_id) == str else document_id + document_id = UUID(document_id) if isinstance(document_id, str) else document_id if document_id is None or document_id in own_document_ids: filtered_search_results.append(search_result) - await log_result(query.id, json.dumps(filtered_search_results, cls = JSONEncoder), user.id) + await log_result(query.id, json.dumps(filtered_search_results, cls=JSONEncoder), user.id) return filtered_search_results + async def specific_search(query_type: SearchType, query: str, user) -> list: search_tasks: Dict[SearchType, Callable] = { SearchType.SUMMARIES: query_summaries, diff --git a/cognee/api/v1/settings/routers/__init__.py b/cognee/api/v1/settings/routers/__init__.py index 363d26610..4cb4fc49b 100644 --- a/cognee/api/v1/settings/routers/__init__.py +++ b/cognee/api/v1/settings/routers/__init__.py @@ -1 +1 @@ -from .get_settings_router import get_settings_router \ No newline at end of file +from .get_settings_router import get_settings_router diff --git a/cognee/api/v1/settings/routers/get_settings_router.py b/cognee/api/v1/settings/routers/get_settings_router.py index 31692382b..138bea661 100644 --- a/cognee/api/v1/settings/routers/get_settings_router.py +++ b/cognee/api/v1/settings/routers/get_settings_router.py @@ -6,40 +6,50 @@ from cognee.modules.users.models import User from cognee.modules.settings.get_settings import LLMConfig, VectorDBConfig + class LLMConfigOutputDTO(OutDTO, LLMConfig): pass + class VectorDBConfigOutputDTO(OutDTO, VectorDBConfig): pass + class SettingsDTO(OutDTO): llm: LLMConfigOutputDTO vector_db: VectorDBConfigOutputDTO + class LLMConfigInputDTO(InDTO): provider: Union[Literal["openai"], Literal["ollama"], Literal["anthropic"]] model: str api_key: str + class VectorDBConfigInputDTO(InDTO): provider: Union[Literal["lancedb"], Literal["qdrant"], Literal["weaviate"], Literal["pgvector"]] url: str api_key: str + class SettingsPayloadDTO(InDTO): llm: Optional[LLMConfigInputDTO] = None vector_db: Optional[VectorDBConfigInputDTO] = None + def get_settings_router() -> APIRouter: router = APIRouter() @router.get("/", response_model=SettingsDTO) async def get_settings(user: User = Depends(get_authenticated_user)): from cognee.modules.settings import get_settings as get_cognee_settings + return get_cognee_settings() @router.post("/", response_model=None) - async def save_settings(new_settings: SettingsPayloadDTO, user: User = Depends(get_authenticated_user)): + async def save_settings( + new_settings: SettingsPayloadDTO, user: User = Depends(get_authenticated_user) + ): from cognee.modules.settings import save_llm_config, save_vector_db_config if new_settings.llm is not None: @@ -48,4 +58,4 @@ async def save_settings(new_settings: SettingsPayloadDTO, user: User = Depends(g if new_settings.vector_db is not None: await save_vector_db_config(new_settings.vector_db) - return router \ No newline at end of file + return router diff --git a/cognee/api/v1/users/create_user.py b/cognee/api/v1/users/create_user.py index eba7a6e89..adfbf4ee1 100644 --- a/cognee/api/v1/users/create_user.py +++ b/cognee/api/v1/users/create_user.py @@ -3,10 +3,10 @@ async def create_user(email: str, password: str, is_superuser: bool = False): user = await create_user_method( - email = email, - password = password, - is_superuser = is_superuser, - is_verified = True, + email=email, + password=password, + is_superuser=is_superuser, + is_verified=True, ) return user diff --git a/cognee/api/v1/users/routers/__init__.py b/cognee/api/v1/users/routers/__init__.py index 482aac265..ae4b7ca56 100644 --- a/cognee/api/v1/users/routers/__init__.py +++ b/cognee/api/v1/users/routers/__init__.py @@ -3,3 +3,4 @@ from .get_reset_password_router import get_reset_password_router from .get_users_router import get_users_router from .get_verify_router import get_verify_router +from .get_visualize_router import get_visualize_router diff --git a/cognee/api/v1/users/routers/get_auth_router.py b/cognee/api/v1/users/routers/get_auth_router.py index 8a65cde35..b6acc62a6 100644 --- a/cognee/api/v1/users/routers/get_auth_router.py +++ b/cognee/api/v1/users/routers/get_auth_router.py @@ -1,6 +1,7 @@ from cognee.modules.users.get_fastapi_users import get_fastapi_users from cognee.modules.users.authentication.get_auth_backend import get_auth_backend + def get_auth_router(): auth_backend = get_auth_backend() return get_fastapi_users().get_auth_router(auth_backend) diff --git a/cognee/api/v1/users/routers/get_register_router.py b/cognee/api/v1/users/routers/get_register_router.py index a1152c01c..8857b01da 100644 --- a/cognee/api/v1/users/routers/get_register_router.py +++ b/cognee/api/v1/users/routers/get_register_router.py @@ -1,5 +1,6 @@ from cognee.modules.users.get_fastapi_users import get_fastapi_users from cognee.modules.users.models.User import UserRead, UserCreate + def get_register_router(): return get_fastapi_users().get_register_router(UserRead, UserCreate) diff --git a/cognee/api/v1/users/routers/get_reset_password_router.py b/cognee/api/v1/users/routers/get_reset_password_router.py index c058abe2a..33708c72a 100644 --- a/cognee/api/v1/users/routers/get_reset_password_router.py +++ b/cognee/api/v1/users/routers/get_reset_password_router.py @@ -1,4 +1,5 @@ from cognee.modules.users.get_fastapi_users import get_fastapi_users + def get_reset_password_router(): return get_fastapi_users().get_reset_password_router() diff --git a/cognee/api/v1/users/routers/get_users_router.py b/cognee/api/v1/users/routers/get_users_router.py index b81be73b0..776aefba0 100644 --- a/cognee/api/v1/users/routers/get_users_router.py +++ b/cognee/api/v1/users/routers/get_users_router.py @@ -1,5 +1,6 @@ from cognee.modules.users.get_fastapi_users import get_fastapi_users from cognee.modules.users.models.User import UserRead, UserUpdate + def get_users_router(): return get_fastapi_users().get_users_router(UserRead, UserUpdate) diff --git a/cognee/api/v1/users/routers/get_verify_router.py b/cognee/api/v1/users/routers/get_verify_router.py index 0c18b08c2..aaf52b339 100644 --- a/cognee/api/v1/users/routers/get_verify_router.py +++ b/cognee/api/v1/users/routers/get_verify_router.py @@ -1,5 +1,6 @@ from cognee.modules.users.get_fastapi_users import get_fastapi_users from cognee.modules.users.models.User import UserRead + def get_verify_router(): return get_fastapi_users().get_verify_router(UserRead) diff --git a/cognee/api/v1/users/routers/get_visualize_router.py b/cognee/api/v1/users/routers/get_visualize_router.py new file mode 100644 index 000000000..3e678cccd --- /dev/null +++ b/cognee/api/v1/users/routers/get_visualize_router.py @@ -0,0 +1,32 @@ +from fastapi import Form, UploadFile, Depends +from fastapi.responses import JSONResponse +from fastapi import APIRouter +from typing import List +import aiohttp +import subprocess +import logging +import os +from cognee.modules.users.models import User +from cognee.modules.users.methods import get_authenticated_user + +logger = logging.getLogger(__name__) + + +def get_visualize_router() -> APIRouter: + router = APIRouter() + + @router.post("/", response_model=None) + async def visualize( + user: User = Depends(get_authenticated_user), + ): + """This endpoint is responsible for adding data to the graph.""" + from cognee.api.v1.visualize import visualize_graph + + try: + html_visualization = await visualize_graph() + return html_visualization + + except Exception as error: + return JSONResponse(status_code=409, content={"error": str(error)}) + + return router diff --git a/cognee/api/v1/visualize/__init__.py b/cognee/api/v1/visualize/__init__.py new file mode 100644 index 000000000..80db0367c --- /dev/null +++ b/cognee/api/v1/visualize/__init__.py @@ -0,0 +1 @@ +from .visualize import visualize_graph diff --git a/cognee/api/v1/visualize/visualize.py b/cognee/api/v1/visualize/visualize.py new file mode 100644 index 000000000..3267f9d3d --- /dev/null +++ b/cognee/api/v1/visualize/visualize.py @@ -0,0 +1,14 @@ +from cognee.shared.utils import create_cognee_style_network_with_logo, graph_to_tuple +from cognee.infrastructure.databases.graph import get_graph_engine +import logging + + +async def visualize_graph(label: str = "name"): + """ """ + graph_engine = await get_graph_engine() + graph_data = await graph_engine.get_graph_data() + logging.info(graph_data) + + graph = await create_cognee_style_network_with_logo(graph_data, label=label) + + return graph diff --git a/cognee/base_config.py b/cognee/base_config.py index f50f5a13d..6b1b8811e 100644 --- a/cognee/base_config.py +++ b/cognee/base_config.py @@ -5,6 +5,7 @@ from cognee.root_dir import get_absolute_path from cognee.shared.data_models import MonitoringTool + class BaseConfig(BaseSettings): data_root_directory: str = get_absolute_path(".data_storage") monitoring_tool: object = MonitoringTool.LANGFUSE @@ -13,7 +14,7 @@ class BaseConfig(BaseSettings): langfuse_public_key: Optional[str] = os.getenv("LANGFUSE_PUBLIC_KEY") langfuse_secret_key: Optional[str] = os.getenv("LANGFUSE_SECRET_KEY") langfuse_host: Optional[str] = os.getenv("LANGFUSE_HOST") - model_config = SettingsConfigDict(env_file = ".env", extra = "allow") + model_config = SettingsConfigDict(env_file=".env", extra="allow") def to_dict(self) -> dict: return { @@ -21,6 +22,7 @@ def to_dict(self) -> dict: "monitoring_tool": self.monitoring_tool, } + @lru_cache def get_base_config(): return BaseConfig() diff --git a/cognee/exceptions/__init__.py b/cognee/exceptions/__init__.py index 40120e0e1..1432afcc8 100644 --- a/cognee/exceptions/__init__.py +++ b/cognee/exceptions/__init__.py @@ -10,4 +10,4 @@ ServiceError, InvalidValueError, InvalidAttributeError, -) \ No newline at end of file +) diff --git a/cognee/exceptions/exceptions.py b/cognee/exceptions/exceptions.py index f94daf8c9..c4ce73f8c 100644 --- a/cognee/exceptions/exceptions.py +++ b/cognee/exceptions/exceptions.py @@ -3,6 +3,7 @@ logger = logging.getLogger(__name__) + class CogneeApiError(Exception): """Base exception class""" @@ -36,19 +37,19 @@ def __init__( class InvalidValueError(CogneeApiError): def __init__( - self, - message: str = "Invalid Value.", - name: str = "InvalidValueError", - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + self, + message: str = "Invalid Value.", + name: str = "InvalidValueError", + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, ): super().__init__(message, name, status_code) class InvalidAttributeError(CogneeApiError): def __init__( - self, - message: str = "Invalid attribute.", - name: str = "InvalidAttributeError", - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + self, + message: str = "Invalid attribute.", + name: str = "InvalidAttributeError", + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, ): - super().__init__(message, name, status_code) \ No newline at end of file + super().__init__(message, name, status_code) diff --git a/cognee/fetch_secret.py b/cognee/fetch_secret.py index 65b5326a9..c36f9e4ce 100644 --- a/cognee/fetch_secret.py +++ b/cognee/fetch_secret.py @@ -15,7 +15,7 @@ environment = os.getenv("AWS_ENV", "dev") -def fetch_secret(secret_name:str, region_name:str, env_file_path:str): +def fetch_secret(secret_name: str, region_name: str, env_file_path: str): """Fetch the secret from AWS Secrets Manager and write it to the .env file.""" print("Initializing session") session = boto3.session.Session() diff --git a/cognee/infrastructure/data/chunking/DefaultChunkEngine.py b/cognee/infrastructure/data/chunking/DefaultChunkEngine.py index 2ca879b25..0d67d5e38 100644 --- a/cognee/infrastructure/data/chunking/DefaultChunkEngine.py +++ b/cognee/infrastructure/data/chunking/DefaultChunkEngine.py @@ -1,4 +1,5 @@ -""" Chunking strategies for splitting text into smaller parts.""" +"""Chunking strategies for splitting text into smaller parts.""" + from __future__ import annotations import re from cognee.shared.data_models import ChunkStrategy @@ -6,17 +7,15 @@ # /Users/vasa/Projects/cognee/cognee/infrastructure/data/chunking/DefaultChunkEngine.py -class DefaultChunkEngine(): + +class DefaultChunkEngine: def __init__(self, chunk_strategy=None, chunk_size=None, chunk_overlap=None): self.chunk_strategy = chunk_strategy self.chunk_size = chunk_size self.chunk_overlap = chunk_overlap - @staticmethod - def _split_text_with_regex( - text: str, separator: str, keep_separator: bool - ) -> list[str]: + def _split_text_with_regex(text: str, separator: str, keep_separator: bool) -> list[str]: # Now that we have the separator, split the text if separator: if keep_separator: @@ -32,13 +31,12 @@ def _split_text_with_regex( splits = list(text) return [s for s in splits if s != ""] - - - def chunk_data(self, - chunk_strategy = None, - source_data = None, - chunk_size = None, - chunk_overlap = None, + def chunk_data( + self, + chunk_strategy=None, + source_data=None, + chunk_size=None, + chunk_overlap=None, ): """ Chunk data based on the specified strategy. @@ -54,44 +52,47 @@ def chunk_data(self, """ if self.chunk_strategy == ChunkStrategy.PARAGRAPH: - chunked_data, chunk_number = self.chunk_data_by_paragraph(source_data,chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap) + chunked_data, chunk_number = self.chunk_data_by_paragraph( + source_data, chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap + ) elif self.chunk_strategy == ChunkStrategy.SENTENCE: - chunked_data, chunk_number = self.chunk_by_sentence(source_data, chunk_size = self.chunk_size, chunk_overlap=self.chunk_overlap) + chunked_data, chunk_number = self.chunk_by_sentence( + source_data, chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap + ) elif self.chunk_strategy == ChunkStrategy.EXACT: - chunked_data, chunk_number = self.chunk_data_exact(source_data, chunk_size = self.chunk_size, chunk_overlap=self.chunk_overlap) + chunked_data, chunk_number = self.chunk_data_exact( + source_data, chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap + ) else: chunked_data, chunk_number = "Invalid chunk strategy.", [0, "Invalid chunk strategy."] - return chunked_data, chunk_number - - def chunk_data_exact(self, data_chunks, chunk_size, chunk_overlap): data = "".join(data_chunks) chunks = [] for i in range(0, len(data), chunk_size - chunk_overlap): - chunks.append(data[i:i + chunk_size]) + chunks.append(data[i : i + chunk_size]) numbered_chunks = [] for i, chunk in enumerate(chunks): numbered_chunk = [i + 1, chunk] numbered_chunks.append(numbered_chunk) return chunks, numbered_chunks - - def chunk_by_sentence(self, data_chunks, chunk_size, chunk_overlap): # Split by periods, question marks, exclamation marks, and ellipses data = "".join(data_chunks) # The regular expression is used to find series of charaters that end with one the following chaacters (. ! ? ...) - sentence_endings = r'(?<=[.!?…]) +' + sentence_endings = r"(?<=[.!?…]) +" sentences = re.split(sentence_endings, data) sentence_chunks = [] for sentence in sentences: if len(sentence) > chunk_size: - chunks = self.chunk_data_exact(data_chunks=[sentence], chunk_size=chunk_size, chunk_overlap=chunk_overlap) + chunks = self.chunk_data_exact( + data_chunks=[sentence], chunk_size=chunk_size, chunk_overlap=chunk_overlap + ) sentence_chunks.extend(chunks) else: sentence_chunks.append(sentence) @@ -102,9 +103,7 @@ def chunk_by_sentence(self, data_chunks, chunk_size, chunk_overlap): numbered_chunks.append(numbered_chunk) return sentence_chunks, numbered_chunks - - - def chunk_data_by_paragraph(self, data_chunks, chunk_size, chunk_overlap, bound = 0.75): + def chunk_data_by_paragraph(self, data_chunks, chunk_size, chunk_overlap, bound=0.75): data = "".join(data_chunks) total_length = len(data) chunks = [] diff --git a/cognee/infrastructure/data/chunking/LangchainChunkingEngine.py b/cognee/infrastructure/data/chunking/LangchainChunkingEngine.py index c58f7517c..5de642ca8 100644 --- a/cognee/infrastructure/data/chunking/LangchainChunkingEngine.py +++ b/cognee/infrastructure/data/chunking/LangchainChunkingEngine.py @@ -5,7 +5,6 @@ from cognee.shared.data_models import ChunkStrategy - class LangchainChunkEngine: def __init__(self, chunk_strategy=None, source_data=None, chunk_size=None, chunk_overlap=None): self.chunk_strategy = chunk_strategy @@ -13,13 +12,12 @@ def __init__(self, chunk_strategy=None, source_data=None, chunk_size=None, chunk self.chunk_size = chunk_size self.chunk_overlap = chunk_overlap - - - def chunk_data(self, - chunk_strategy = None, - source_data = None, - chunk_size = None, - chunk_overlap = None, + def chunk_data( + self, + chunk_strategy=None, + source_data=None, + chunk_size=None, + chunk_overlap=None, ): """ Chunk data based on the specified strategy. @@ -35,20 +33,24 @@ def chunk_data(self, """ if chunk_strategy == ChunkStrategy.CODE: - chunked_data, chunk_number = self.chunk_data_by_code(source_data,self.chunk_size, self.chunk_overlap) + chunked_data, chunk_number = self.chunk_data_by_code( + source_data, self.chunk_size, self.chunk_overlap + ) elif chunk_strategy == ChunkStrategy.LANGCHAIN_CHARACTER: - chunked_data, chunk_number = self.chunk_data_by_character(source_data,self.chunk_size, self.chunk_overlap) + chunked_data, chunk_number = self.chunk_data_by_character( + source_data, self.chunk_size, self.chunk_overlap + ) else: - chunked_data, chunk_number = "Invalid chunk strategy.", [0, "Invalid chunk strategy."] + chunked_data, chunk_number = "Invalid chunk strategy.", [0, "Invalid chunk strategy."] return chunked_data, chunk_number - - def chunk_data_by_code(self, data_chunks, chunk_size, chunk_overlap= 10, language=None): + def chunk_data_by_code(self, data_chunks, chunk_size, chunk_overlap=10, language=None): from langchain_text_splitters import ( Language, RecursiveCharacterTextSplitter, ) + if language is None: language = Language.PYTHON python_splitter = RecursiveCharacterTextSplitter.from_language( @@ -67,7 +69,10 @@ def chunk_data_by_code(self, data_chunks, chunk_size, chunk_overlap= 10, languag def chunk_data_by_character(self, data_chunks, chunk_size=1500, chunk_overlap=10): from langchain_text_splitters import RecursiveCharacterTextSplitter - splitter = RecursiveCharacterTextSplitter(chunk_size =chunk_size, chunk_overlap=chunk_overlap) + + splitter = RecursiveCharacterTextSplitter( + chunk_size=chunk_size, chunk_overlap=chunk_overlap + ) data_chunks = splitter.create_documents([data_chunks]) only_content = [chunk.page_content for chunk in data_chunks] @@ -78,4 +83,3 @@ def chunk_data_by_character(self, data_chunks, chunk_size=1500, chunk_overlap=10 numbered_chunks.append(numbered_chunk) return only_content, numbered_chunks - diff --git a/cognee/infrastructure/data/chunking/config.py b/cognee/infrastructure/data/chunking/config.py index b917b8ddd..e367e4de8 100644 --- a/cognee/infrastructure/data/chunking/config.py +++ b/cognee/infrastructure/data/chunking/config.py @@ -11,8 +11,7 @@ class ChunkConfig(BaseSettings): chunk_strategy: object = ChunkStrategy.PARAGRAPH chunk_engine: object = ChunkEngine.DEFAULT_ENGINE - - model_config = SettingsConfigDict(env_file = ".env", extra = "allow") + model_config = SettingsConfigDict(env_file=".env", extra="allow") def to_dict(self) -> dict: return { diff --git a/cognee/infrastructure/data/chunking/create_chunking_engine.py b/cognee/infrastructure/data/chunking/create_chunking_engine.py index 5cd0d9077..7966bbf6c 100644 --- a/cognee/infrastructure/data/chunking/create_chunking_engine.py +++ b/cognee/infrastructure/data/chunking/create_chunking_engine.py @@ -9,9 +9,11 @@ class ChunkingConfig(Dict): vector_db_key: str vector_db_provider: str + def create_chunking_engine(config: ChunkingConfig): if config["chunk_engine"] == ChunkEngine.LANGCHAIN_ENGINE: from cognee.infrastructure.data.chunking.LangchainChunkingEngine import LangchainChunkEngine + return LangchainChunkEngine( chunk_size=config["chunk_size"], chunk_overlap=config["chunk_overlap"], diff --git a/cognee/infrastructure/data/chunking/get_chunking_engine.py b/cognee/infrastructure/data/chunking/get_chunking_engine.py index d188ffa9b..8d40a35a9 100644 --- a/cognee/infrastructure/data/chunking/get_chunking_engine.py +++ b/cognee/infrastructure/data/chunking/get_chunking_engine.py @@ -2,5 +2,6 @@ from .create_chunking_engine import create_chunking_engine + def get_chunk_engine(): return create_chunking_engine(get_chunk_config().to_dict()) diff --git a/cognee/infrastructure/data/utils/extract_keywords.py b/cognee/infrastructure/data/utils/extract_keywords.py index 11f061889..42b06884a 100644 --- a/cognee/infrastructure/data/utils/extract_keywords.py +++ b/cognee/infrastructure/data/utils/extract_keywords.py @@ -3,6 +3,7 @@ from cognee.exceptions import InvalidValueError from cognee.shared.utils import extract_pos_tags + def extract_keywords(text: str) -> list[str]: if len(text) == 0: raise InvalidValueError(message="extract_keywords cannot extract keywords from empty text.") @@ -14,9 +15,7 @@ def extract_keywords(text: str) -> list[str]: tfidf = vectorizer.fit_transform(nouns) top_nouns = sorted( - vectorizer.vocabulary_, - key = lambda x: tfidf[0, vectorizer.vocabulary_[x]], - reverse = True + vectorizer.vocabulary_, key=lambda x: tfidf[0, vectorizer.vocabulary_[x]], reverse=True ) keywords = [] diff --git a/cognee/infrastructure/databases/exceptions/EmbeddingException.py b/cognee/infrastructure/databases/exceptions/EmbeddingException.py index ba7c70d80..130282857 100644 --- a/cognee/infrastructure/databases/exceptions/EmbeddingException.py +++ b/cognee/infrastructure/databases/exceptions/EmbeddingException.py @@ -1,3 +1,4 @@ class EmbeddingException(Exception): """Custom exception for handling embedding-related errors.""" - pass \ No newline at end of file + + pass diff --git a/cognee/infrastructure/databases/exceptions/__init__.py b/cognee/infrastructure/databases/exceptions/__init__.py index 5836e7d11..7c74db3df 100644 --- a/cognee/infrastructure/databases/exceptions/__init__.py +++ b/cognee/infrastructure/databases/exceptions/__init__.py @@ -7,4 +7,4 @@ from .exceptions import ( EntityNotFoundError, EntityAlreadyExistsError, -) \ No newline at end of file +) diff --git a/cognee/infrastructure/databases/exceptions/exceptions.py b/cognee/infrastructure/databases/exceptions/exceptions.py index af15bb616..854e620ff 100644 --- a/cognee/infrastructure/databases/exceptions/exceptions.py +++ b/cognee/infrastructure/databases/exceptions/exceptions.py @@ -1,6 +1,7 @@ from cognee.exceptions import CogneeApiError from fastapi import status + class EntityNotFoundError(CogneeApiError): """Database returns nothing""" @@ -22,4 +23,4 @@ def __init__( name: str = "EntityAlreadyExistsError", status_code=status.HTTP_409_CONFLICT, ): - super().__init__(message, name, status_code) \ No newline at end of file + super().__init__(message, name, status_code) diff --git a/cognee/infrastructure/databases/graph/config.py b/cognee/infrastructure/databases/graph/config.py index 8e6da37c7..b24a9e964 100644 --- a/cognee/infrastructure/databases/graph/config.py +++ b/cognee/infrastructure/databases/graph/config.py @@ -1,4 +1,4 @@ -""" This module contains the configuration for the graph database. """ +"""This module contains the configuration for the graph database.""" import os from functools import lru_cache @@ -15,8 +15,7 @@ class GraphConfig(BaseSettings): graph_database_password: str = "" graph_database_port: int = 123 graph_file_path: str = os.path.join( - os.path.join(get_absolute_path(".cognee_system"), "databases"), - graph_filename + os.path.join(get_absolute_path(".cognee_system"), "databases"), graph_filename ) graph_model: object = KnowledgeGraph graph_topology: object = KnowledgeGraph diff --git a/cognee/infrastructure/databases/graph/get_graph_engine.py b/cognee/infrastructure/databases/graph/get_graph_engine.py index 10c16bbdd..4660a610f 100644 --- a/cognee/infrastructure/databases/graph/get_graph_engine.py +++ b/cognee/infrastructure/databases/graph/get_graph_engine.py @@ -1,4 +1,5 @@ """Factory function to get the appropriate graph client based on the graph type.""" + from functools import lru_cache from .config import get_graph_config @@ -26,7 +27,11 @@ def create_graph_engine() -> GraphDBInterface: config = get_graph_config() if config.graph_database_provider == "neo4j": - if not (config.graph_database_url and config.graph_database_username and config.graph_database_password): + if not ( + config.graph_database_url + and config.graph_database_username + and config.graph_database_password + ): raise EnvironmentError("Missing required Neo4j credentials.") from .neo4j_driver.adapter import Neo4jAdapter @@ -34,7 +39,7 @@ def create_graph_engine() -> GraphDBInterface: return Neo4jAdapter( graph_database_url=config.graph_database_url, graph_database_username=config.graph_database_username, - graph_database_password=config.graph_database_password + graph_database_password=config.graph_database_password, ) elif config.graph_database_provider == "falkordb": @@ -53,6 +58,7 @@ def create_graph_engine() -> GraphDBInterface: ) from .networkx.adapter import NetworkXAdapter + graph_client = NetworkXAdapter(filename=config.graph_file_path) return graph_client diff --git a/cognee/infrastructure/databases/graph/graph_db_interface.py b/cognee/infrastructure/databases/graph/graph_db_interface.py index bcc09658c..30acc1b95 100644 --- a/cognee/infrastructure/databases/graph/graph_db_interface.py +++ b/cognee/infrastructure/databases/graph/graph_db_interface.py @@ -1,47 +1,35 @@ from typing import Protocol, Optional, Dict, Any from abc import abstractmethod + class GraphDBInterface(Protocol): @abstractmethod async def query(self, query: str, params: dict): raise NotImplementedError @abstractmethod - async def add_node( - self, - node_id: str, - node_properties: dict - ): raise NotImplementedError + async def add_node(self, node_id: str, node_properties: dict): + raise NotImplementedError @abstractmethod - async def add_nodes( - self, - nodes: list[tuple[str, dict]] - ): raise NotImplementedError + async def add_nodes(self, nodes: list[tuple[str, dict]]): + raise NotImplementedError @abstractmethod - async def delete_node( - self, - node_id: str - ): raise NotImplementedError + async def delete_node(self, node_id: str): + raise NotImplementedError @abstractmethod - async def delete_nodes( - self, - node_ids: list[str] - ): raise NotImplementedError + async def delete_nodes(self, node_ids: list[str]): + raise NotImplementedError @abstractmethod - async def extract_node( - self, - node_id: str - ): raise NotImplementedError + async def extract_node(self, node_id: str): + raise NotImplementedError @abstractmethod - async def extract_nodes( - self, - node_ids: list[str] - ): raise NotImplementedError + async def extract_nodes(self, node_ids: list[str]): + raise NotImplementedError @abstractmethod async def add_edge( @@ -49,21 +37,20 @@ async def add_edge( from_node: str, to_node: str, relationship_name: str, - edge_properties: Optional[Dict[str, Any]] = None - ): raise NotImplementedError + edge_properties: Optional[Dict[str, Any]] = None, + ): + raise NotImplementedError @abstractmethod - async def add_edges( - self, - edges: tuple[str, str, str, dict] - ): raise NotImplementedError + async def add_edges(self, edges: tuple[str, str, str, dict]): + raise NotImplementedError @abstractmethod async def delete_graph( self, - ): raise NotImplementedError + ): + raise NotImplementedError @abstractmethod - async def get_graph_data( - self - ): raise NotImplementedError + async def get_graph_data(self): + raise NotImplementedError diff --git a/cognee/infrastructure/databases/graph/neo4j_driver/adapter.py b/cognee/infrastructure/databases/graph/neo4j_driver/adapter.py index e6520e4e2..5490f6b43 100644 --- a/cognee/infrastructure/databases/graph/neo4j_driver/adapter.py +++ b/cognee/infrastructure/databases/graph/neo4j_driver/adapter.py @@ -1,4 +1,5 @@ -""" Neo4j Adapter for Graph Database""" +"""Neo4j Adapter for Graph Database""" + import logging import asyncio from textwrap import dedent @@ -13,6 +14,7 @@ logger = logging.getLogger("Neo4jAdapter") + class Neo4jAdapter(GraphDBInterface): def __init__( self, @@ -23,8 +25,8 @@ def __init__( ): self.driver = driver or AsyncGraphDatabase.driver( graph_database_url, - auth = (graph_database_username, graph_database_password), - max_connection_lifetime = 120 + auth=(graph_database_username, graph_database_password), + max_connection_lifetime=120, ) @asynccontextmanager @@ -39,11 +41,11 @@ async def query( ) -> List[Dict[str, Any]]: try: async with self.get_session() as session: - result = await session.run(query, parameters = params) + result = await session.run(query, parameters=params) data = await result.data() return data except Neo4jError as error: - logger.error("Neo4j query error: %s", error, exc_info = True) + logger.error("Neo4j query error: %s", error, exc_info=True) raise error async def has_node(self, node_id: str) -> bool: @@ -53,7 +55,7 @@ async def has_node(self, node_id: str) -> bool: WHERE n.id = $node_id RETURN COUNT(n) > 0 AS node_exists """, - {"node_id": node_id} + {"node_id": node_id}, ) return results[0]["node_exists"] if len(results) > 0 else False @@ -83,15 +85,17 @@ async def add_nodes(self, nodes: list[DataPoint]) -> None: RETURN ID(labeledNode) AS internal_id, labeledNode.id AS nodeId """ - nodes = [{ - "node_id": str(node.id), - "properties": self.serialize_properties(node.model_dump()), - } for node in nodes] + nodes = [ + { + "node_id": str(node.id), + "properties": self.serialize_properties(node.model_dump()), + } + for node in nodes + ] - results = await self.query(query, dict(nodes = nodes)) + results = await self.query(query, dict(nodes=nodes)) return results - async def extract_node(self, node_id: str): results = await self.extract_nodes([node_id]) @@ -103,9 +107,7 @@ async def extract_nodes(self, node_ids: List[str]): MATCH (node {id: id}) RETURN node""" - params = { - "node_ids": node_ids - } + params = {"node_ids": node_ids} results = await self.query(query, params) @@ -115,7 +117,7 @@ async def delete_node(self, node_id: str): node_id = id.replace(":", "_") query = f"MATCH (node:`{node_id}` {{id: $node_id}}) DETACH DELETE n" - params = { "node_id": node_id } + params = {"node_id": node_id} return await self.query(query, params) @@ -125,9 +127,7 @@ async def delete_nodes(self, node_ids: list[str]) -> None: MATCH (node {id: id}) DETACH DELETE node""" - params = { - "node_ids": node_ids - } + params = {"node_ids": node_ids} return await self.query(query, params) @@ -157,21 +157,29 @@ async def has_edges(self, edges): try: params = { - "edges": [{ - "from_node": str(edge[0]), - "to_node": str(edge[1]), - "relationship_name": edge[2], - } for edge in edges], + "edges": [ + { + "from_node": str(edge[0]), + "to_node": str(edge[1]), + "relationship_name": edge[2], + } + for edge in edges + ], } results = await self.query(query, params) return [result["edge_exists"] for result in results] except Neo4jError as error: - logger.error("Neo4j query error: %s", error, exc_info = True) + logger.error("Neo4j query error: %s", error, exc_info=True) raise error - - async def add_edge(self, from_node: UUID, to_node: UUID, relationship_name: str, edge_properties: Optional[Dict[str, Any]] = {}): + async def add_edge( + self, + from_node: UUID, + to_node: UUID, + relationship_name: str, + edge_properties: Optional[Dict[str, Any]] = {}, + ): serialized_properties = self.serialize_properties(edge_properties) query = dedent("""MATCH (from_node {id: $from_node}), @@ -186,12 +194,11 @@ async def add_edge(self, from_node: UUID, to_node: UUID, relationship_name: str, "from_node": str(from_node), "to_node": str(to_node), "relationship_name": relationship_name, - "properties": serialized_properties + "properties": serialized_properties, } return await self.query(query, params) - async def add_edges(self, edges: list[tuple[str, str, str, dict[str, Any]]]) -> None: query = """ UNWIND $edges AS edge @@ -201,22 +208,25 @@ async def add_edges(self, edges: list[tuple[str, str, str, dict[str, Any]]]) -> RETURN rel """ - edges = [{ - "from_node": str(edge[0]), - "to_node": str(edge[1]), - "relationship_name": edge[2], - "properties": { - **(edge[3] if edge[3] else {}), - "source_node_id": str(edge[0]), - "target_node_id": str(edge[1]), - }, - } for edge in edges] + edges = [ + { + "from_node": str(edge[0]), + "to_node": str(edge[1]), + "relationship_name": edge[2], + "properties": { + **(edge[3] if edge[3] else {}), + "source_node_id": str(edge[0]), + "target_node_id": str(edge[1]), + }, + } + for edge in edges + ] try: - results = await self.query(query, dict(edges = edges)) + results = await self.query(query, dict(edges=edges)) return results except Neo4jError as error: - logger.error("Neo4j query error: %s", error, exc_info = True) + logger.error("Neo4j query error: %s", error, exc_info=True) raise error async def get_edges(self, node_id: str): @@ -225,9 +235,12 @@ async def get_edges(self, node_id: str): RETURN n, r, m """ - results = await self.query(query, dict(node_id = node_id)) + results = await self.query(query, dict(node_id=node_id)) - return [(result["n"]["id"], result["m"]["id"], {"relationship_name": result["r"][1]}) for result in results] + return [ + (result["n"]["id"], result["m"]["id"], {"relationship_name": result["r"][1]}) + for result in results + ] async def get_disconnected_nodes(self) -> list[str]: # return await self.query( @@ -267,7 +280,6 @@ async def get_disconnected_nodes(self) -> list[str]: results = await self.query(query) return results[0]["ids"] if len(results) > 0 else [] - async def get_predecessors(self, node_id: str, edge_label: str = None) -> list[str]: if edge_label is not None: query = """ @@ -279,9 +291,9 @@ async def get_predecessors(self, node_id: str, edge_label: str = None) -> list[s results = await self.query( query, dict( - node_id = node_id, - edge_label = edge_label, - ) + node_id=node_id, + edge_label=edge_label, + ), ) return [result["predecessor"] for result in results] @@ -295,8 +307,8 @@ async def get_predecessors(self, node_id: str, edge_label: str = None) -> list[s results = await self.query( query, dict( - node_id = node_id, - ) + node_id=node_id, + ), ) return [result["predecessor"] for result in results] @@ -312,8 +324,8 @@ async def get_successors(self, node_id: str, edge_label: str = None) -> list[str results = await self.query( query, dict( - node_id = node_id, - edge_label = edge_label, + node_id=node_id, + edge_label=edge_label, ), ) @@ -328,14 +340,16 @@ async def get_successors(self, node_id: str, edge_label: str = None) -> list[str results = await self.query( query, dict( - node_id = node_id, - ) + node_id=node_id, + ), ) return [result["successor"] for result in results] async def get_neighbours(self, node_id: str) -> List[Dict[str, Any]]: - predecessors, successors = await asyncio.gather(self.get_predecessors(node_id), self.get_successors(node_id)) + predecessors, successors = await asyncio.gather( + self.get_predecessors(node_id), self.get_successors(node_id) + ) return predecessors + successors @@ -352,52 +366,55 @@ async def get_connections(self, node_id: UUID) -> list: """ predecessors, successors = await asyncio.gather( - self.query(predecessors_query, dict(node_id = str(node_id))), - self.query(successors_query, dict(node_id = str(node_id))), + self.query(predecessors_query, dict(node_id=str(node_id))), + self.query(successors_query, dict(node_id=str(node_id))), ) connections = [] for neighbour in predecessors: neighbour = neighbour["relation"] - connections.append((neighbour[0], { "relationship_name": neighbour[1] }, neighbour[2])) + connections.append((neighbour[0], {"relationship_name": neighbour[1]}, neighbour[2])) for neighbour in successors: neighbour = neighbour["relation"] - connections.append((neighbour[0], { "relationship_name": neighbour[1] }, neighbour[2])) + connections.append((neighbour[0], {"relationship_name": neighbour[1]}, neighbour[2])) return connections - async def remove_connection_to_predecessors_of(self, node_ids: list[str], edge_label: str) -> None: + async def remove_connection_to_predecessors_of( + self, node_ids: list[str], edge_label: str + ) -> None: query = f""" UNWIND $node_ids AS id MATCH (node:`{id}`)-[r:{edge_label}]->(predecessor) DELETE r; """ - params = { "node_ids": node_ids } + params = {"node_ids": node_ids} return await self.query(query, params) - async def remove_connection_to_successors_of(self, node_ids: list[str], edge_label: str) -> None: + async def remove_connection_to_successors_of( + self, node_ids: list[str], edge_label: str + ) -> None: query = f""" UNWIND $node_ids AS id MATCH (node:`{id}`)<-[r:{edge_label}]-(successor) DELETE r; """ - params = { "node_ids": node_ids } + params = {"node_ids": node_ids} return await self.query(query, params) - async def delete_graph(self): query = """MATCH (node) DETACH DELETE node;""" return await self.query(query) - def serialize_properties(self, properties = dict()): + def serialize_properties(self, properties=dict()): serialized_properties = {} for property_key, property_value in properties.items(): @@ -414,22 +431,28 @@ async def get_graph_data(self): result = await self.query(query) - nodes = [( - record["properties"]["id"], - record["properties"], - ) for record in result] + nodes = [ + ( + record["properties"]["id"], + record["properties"], + ) + for record in result + ] query = """ MATCH (n)-[r]->(m) RETURN ID(n) AS source, ID(m) AS target, TYPE(r) AS type, properties(r) AS properties """ result = await self.query(query) - edges = [( - record["properties"]["source_node_id"], - record["properties"]["target_node_id"], - record["type"], - record["properties"], - ) for record in result] + edges = [ + ( + record["properties"]["source_node_id"], + record["properties"]["target_node_id"], + record["type"], + record["properties"], + ) + for record in result + ] return (nodes, edges) @@ -446,7 +469,9 @@ async def get_filtered_graph_data(self, attribute_filters): """ where_clauses = [] for attribute, values in attribute_filters[0].items(): - values_str = ", ".join(f"'{value}'" if isinstance(value, str) else str(value) for value in values) + values_str = ", ".join( + f"'{value}'" if isinstance(value, str) else str(value) for value in values + ) where_clauses.append(f"n.{attribute} IN [{values_str}]") where_clause = " AND ".join(where_clauses) @@ -458,10 +483,13 @@ async def get_filtered_graph_data(self, attribute_filters): """ result_nodes = await self.query(query_nodes) - nodes = [( - record["id"], - record["properties"], - ) for record in result_nodes] + nodes = [ + ( + record["id"], + record["properties"], + ) + for record in result_nodes + ] query_edges = f""" MATCH (n)-[r]->(m) @@ -470,11 +498,14 @@ async def get_filtered_graph_data(self, attribute_filters): """ result_edges = await self.query(query_edges) - edges = [( - record["source"], - record["target"], - record["type"], - record["properties"], - ) for record in result_edges] + edges = [ + ( + record["source"], + record["target"], + record["type"], + record["properties"], + ) + for record in result_edges + ] - return (nodes, edges) \ No newline at end of file + return (nodes, edges) diff --git a/cognee/infrastructure/databases/graph/networkx/adapter.py b/cognee/infrastructure/databases/graph/networkx/adapter.py index aadad639f..75969c798 100644 --- a/cognee/infrastructure/databases/graph/networkx/adapter.py +++ b/cognee/infrastructure/databases/graph/networkx/adapter.py @@ -17,9 +17,10 @@ logger = logging.getLogger("NetworkXAdapter") + class NetworkXAdapter(GraphDBInterface): _instance = None - graph = None # Class variable to store the singleton instance + graph = None # Class variable to store the singleton instance def __new__(cls, filename): if cls._instance is None: @@ -27,12 +28,12 @@ def __new__(cls, filename): cls._instance.filename = filename return cls._instance - def __init__(self, filename = "cognee_graph.pkl"): + def __init__(self, filename="cognee_graph.pkl"): self.filename = filename async def get_graph_data(self): await self.load_graph_from_file() - return (list(self.graph.nodes(data = True)), list(self.graph.edges(data = True, keys = True))) + return (list(self.graph.nodes(data=True)), list(self.graph.edges(data=True, keys=True))) async def query(self, query: str, params: dict): pass @@ -57,24 +58,21 @@ async def add_nodes( self.graph.add_nodes_from(nodes) await self.save_graph_to_file(self.filename) - async def get_graph(self): return self.graph - async def has_edge(self, from_node: str, to_node: str, edge_label: str) -> bool: - return self.graph.has_edge(from_node, to_node, key = edge_label) + return self.graph.has_edge(from_node, to_node, key=edge_label) async def has_edges(self, edges): result = [] - for (from_node, to_node, edge_label) in edges: + for from_node, to_node, edge_label in edges: if self.graph.has_edge(from_node, to_node, edge_label): result.append((from_node, to_node, edge_label)) return result - async def add_edge( self, from_node: str, @@ -83,24 +81,38 @@ async def add_edge( edge_properties: Dict[str, Any] = {}, ) -> None: edge_properties["updated_at"] = datetime.now(timezone.utc) - self.graph.add_edge(from_node, to_node, key = relationship_name, **(edge_properties if edge_properties else {})) + self.graph.add_edge( + from_node, + to_node, + key=relationship_name, + **(edge_properties if edge_properties else {}), + ) await self.save_graph_to_file(self.filename) async def add_edges( self, edges: tuple[str, str, str, dict], ) -> None: - edges = [(edge[0], edge[1], edge[2], { - **(edge[3] if len(edge) == 4 else {}), - "updated_at": datetime.now(timezone.utc), - }) for edge in edges] + edges = [ + ( + edge[0], + edge[1], + edge[2], + { + **(edge[3] if len(edge) == 4 else {}), + "updated_at": datetime.now(timezone.utc), + }, + ) + for edge in edges + ] self.graph.add_edges_from(edges) await self.save_graph_to_file(self.filename) async def get_edges(self, node_id: str): - return list(self.graph.in_edges(node_id, data = True)) + list(self.graph.out_edges(node_id, data = True)) - + return list(self.graph.in_edges(node_id, data=True)) + list( + self.graph.out_edges(node_id, data=True) + ) async def delete_node(self, node_id: str) -> None: """Asynchronously delete a node from the graph if it exists.""" @@ -112,12 +124,11 @@ async def delete_nodes(self, node_ids: List[str]) -> None: self.graph.remove_nodes_from(node_ids) await self.save_graph_to_file(self.filename) - async def get_disconnected_nodes(self) -> List[str]: connected_components = list(nx.weakly_connected_components(self.graph)) disconnected_nodes = [] - biggest_subgraph = max(connected_components, key = len) + biggest_subgraph = max(connected_components, key=len) for component in connected_components: if component != biggest_subgraph: @@ -125,7 +136,6 @@ async def get_disconnected_nodes(self) -> List[str]: return disconnected_nodes - async def extract_node(self, node_id: str) -> dict: if self.graph.has_node(node_id): return self.graph.nodes[node_id] @@ -139,8 +149,8 @@ async def get_predecessors(self, node_id: UUID, edge_label: str = None) -> list: if self.graph.has_node(node_id): if edge_label is None: return [ - self.graph.nodes[predecessor] for predecessor \ - in list(self.graph.predecessors(node_id)) + self.graph.nodes[predecessor] + for predecessor in list(self.graph.predecessors(node_id)) ] nodes = [] @@ -155,8 +165,8 @@ async def get_successors(self, node_id: UUID, edge_label: str = None) -> list: if self.graph.has_node(node_id): if edge_label is None: return [ - self.graph.nodes[successor] for successor \ - in list(self.graph.successors(node_id)) + self.graph.nodes[successor] + for successor in list(self.graph.successors(node_id)) ] nodes = [] @@ -210,7 +220,9 @@ async def get_connections(self, node_id: UUID) -> list: return connections - async def remove_connection_to_predecessors_of(self, node_ids: list[str], edge_label: str) -> None: + async def remove_connection_to_predecessors_of( + self, node_ids: list[str], edge_label: str + ) -> None: for node_id in node_ids: if self.graph.has_node(node_id): for predecessor_id in list(self.graph.predecessors(node_id)): @@ -219,7 +231,9 @@ async def remove_connection_to_predecessors_of(self, node_ids: list[str], edge_l await self.save_graph_to_file(self.filename) - async def remove_connection_to_successors_of(self, node_ids: list[str], edge_label: str) -> None: + async def remove_connection_to_successors_of( + self, node_ids: list[str], edge_label: str + ) -> None: for node_id in node_ids: if self.graph.has_node(node_id): for successor_id in list(self.graph.successors(node_id)): @@ -228,7 +242,7 @@ async def remove_connection_to_successors_of(self, node_ids: list[str], edge_lab await self.save_graph_to_file(self.filename) - async def save_graph_to_file(self, file_path: str=None) -> None: + async def save_graph_to_file(self, file_path: str = None) -> None: """Asynchronously save the graph to a file in JSON format.""" if not file_path: file_path = self.filename @@ -236,8 +250,7 @@ async def save_graph_to_file(self, file_path: str=None) -> None: graph_data = nx.readwrite.json_graph.node_link_data(self.graph) async with aiofiles.open(file_path, "w") as file: - await file.write(json.dumps(graph_data, cls = JSONEncoder)) - + await file.write(json.dumps(graph_data, cls=JSONEncoder)) async def load_graph_from_file(self, file_path: str = None): """Asynchronously load the graph from a file in JSON format.""" @@ -252,50 +265,59 @@ async def load_graph_from_file(self, file_path: str = None): graph_data = json.loads(await file.read()) for node in graph_data["nodes"]: try: - node["id"] = UUID(node["id"]) - except: - pass + node["id"] = UUID(node["id"]) + except Exception as e: + print(e) + pass if "updated_at" in node: - node["updated_at"] = datetime.strptime(node["updated_at"], "%Y-%m-%dT%H:%M:%S.%f%z") + node["updated_at"] = datetime.strptime( + node["updated_at"], "%Y-%m-%dT%H:%M:%S.%f%z" + ) for edge in graph_data["links"]: try: - source_id = UUID(edge["source"]) - target_id = UUID(edge["target"]) + source_id = UUID(edge["source"]) + target_id = UUID(edge["target"]) - edge["source"] = source_id - edge["target"] = target_id - edge["source_node_id"] = source_id - edge["target_node_id"] = target_id - except: - pass + edge["source"] = source_id + edge["target"] = target_id + edge["source_node_id"] = source_id + edge["target_node_id"] = target_id + except Exception as e: + print(e) + pass if "updated_at" in edge: - edge["updated_at"] = datetime.strptime(edge["updated_at"], "%Y-%m-%dT%H:%M:%S.%f%z") + edge["updated_at"] = datetime.strptime( + edge["updated_at"], "%Y-%m-%dT%H:%M:%S.%f%z" + ) self.graph = nx.readwrite.json_graph.node_link_graph(graph_data) for node_id, node_data in self.graph.nodes(data=True): - node_data['id'] = node_id + node_data["id"] = node_id else: # Log that the file does not exist and an empty graph is initialized logger.warning("File %s not found. Initializing an empty graph.", file_path) - self.graph = nx.MultiDiGraph() # Use MultiDiGraph to keep it consistent with __init__ + self.graph = ( + nx.MultiDiGraph() + ) # Use MultiDiGraph to keep it consistent with __init__ file_dir = os.path.dirname(file_path) if not os.path.exists(file_dir): - os.makedirs(file_dir, exist_ok = True) + os.makedirs(file_dir, exist_ok=True) await self.save_graph_to_file(file_path) except Exception: logger.error("Failed to load graph from file: %s", file_path) - async def delete_graph(self, file_path: str = None): """Asynchronously delete the graph file from the filesystem.""" if file_path is None: - file_path = self.filename # Assuming self.filename is defined elsewhere and holds the default graph file path + file_path = ( + self.filename + ) # Assuming self.filename is defined elsewhere and holds the default graph file path try: if os.path.exists(file_path): await aiofiles_os.remove(file_path) @@ -305,7 +327,9 @@ async def delete_graph(self, file_path: str = None): except Exception as error: logger.error("Failed to delete graph: %s", error) - async def get_filtered_graph_data(self, attribute_filters: List[Dict[str, List[Union[str, int]]]]): + async def get_filtered_graph_data( + self, attribute_filters: List[Dict[str, List[Union[str, int]]]] + ): """ Fetches nodes and relationships filtered by specified attribute values. @@ -325,18 +349,21 @@ async def get_filtered_graph_data(self, attribute_filters: List[Dict[str, List[U # Filter nodes filtered_nodes = [ - (node, data) for node, data in self.graph.nodes(data=True) + (node, data) + for node, data in self.graph.nodes(data=True) if all(data.get(attr) in values for attr, values in where_clauses) ] # Filter edges where both source and target nodes satisfy the filters filtered_edges = [ - (source, target, data.get('relationship_type', 'UNKNOWN'), data) + (source, target, data.get("relationship_type", "UNKNOWN"), data) for source, target, data in self.graph.edges(data=True) if ( - all(self.graph.nodes[source].get(attr) in values for attr, values in where_clauses) and - all(self.graph.nodes[target].get(attr) in values for attr, values in where_clauses) + all(self.graph.nodes[source].get(attr) in values for attr, values in where_clauses) + and all( + self.graph.nodes[target].get(attr) in values for attr, values in where_clauses + ) ) ] - return filtered_nodes, filtered_edges \ No newline at end of file + return filtered_nodes, filtered_edges diff --git a/cognee/infrastructure/databases/hybrid/falkordb/FalkorDBAdapter.py b/cognee/infrastructure/databases/hybrid/falkordb/FalkorDBAdapter.py index 324ee7bcd..77bfd74e6 100644 --- a/cognee/infrastructure/databases/hybrid/falkordb/FalkorDBAdapter.py +++ b/cognee/infrastructure/databases/hybrid/falkordb/FalkorDBAdapter.py @@ -1,38 +1,36 @@ import asyncio + # from datetime import datetime import json from textwrap import dedent from uuid import UUID +from webbrowser import Error from falkordb import FalkorDB from cognee.exceptions import InvalidValueError -from cognee.infrastructure.databases.graph.graph_db_interface import \ - GraphDBInterface +from cognee.infrastructure.databases.graph.graph_db_interface import GraphDBInterface from cognee.infrastructure.databases.vector.embeddings import EmbeddingEngine -from cognee.infrastructure.databases.vector.vector_db_interface import \ - VectorDBInterface +from cognee.infrastructure.databases.vector.vector_db_interface import VectorDBInterface from cognee.infrastructure.engine import DataPoint class IndexSchema(DataPoint): text: str - _metadata: dict = { - "index_fields": ["text"], - "type": "IndexSchema" - } + _metadata: dict = {"index_fields": ["text"], "type": "IndexSchema"} + class FalkorDBAdapter(VectorDBInterface, GraphDBInterface): def __init__( self, database_url: str, database_port: int, - embedding_engine = EmbeddingEngine, + embedding_engine=EmbeddingEngine, ): self.driver = FalkorDB( - host = database_url, - port = database_port, + host=database_url, + port=database_port, ) self.embedding_engine = embedding_engine self.graph_name = "cognee_graph" @@ -56,7 +54,11 @@ def parse_value(value): return f"'{str(value)}'" if type(value) is int or type(value) is float: return value - if type(value) is list and type(value[0]) is float and len(value) == self.embedding_engine.get_vector_size(): + if ( + type(value) is list + and type(value[0]) is float + and len(value) == self.embedding_engine.get_vector_size() + ): return f"'vecf32({value})'" # if type(value) is datetime: # return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f%z") @@ -70,14 +72,21 @@ async def create_data_point_query(self, data_point: DataPoint, vectorized_values node_label = type(data_point).__tablename__ property_names = DataPoint.get_embeddable_property_names(data_point) - node_properties = await self.stringify_properties({ - **data_point.model_dump(), - **({ - property_names[index]: (vectorized_values[index] \ - if index < len(vectorized_values) else getattr(data_point, property_name, None)) \ + node_properties = await self.stringify_properties( + { + **data_point.model_dump(), + **( + { + property_names[index]: ( + vectorized_values[index] + if index < len(vectorized_values) + else getattr(data_point, property_name, None) + ) for index, property_name in enumerate(property_names) - }), - }) + } + ), + } + ) return dedent(f""" MERGE (node:{node_label} {{id: '{str(data_point.id)}'}}) @@ -129,12 +138,13 @@ async def create_data_points(self, data_points: list[DataPoint]): await self.create_data_point_query( data_point, [ - vectorized_values[vector_map[str(data_point.id)][property_name]] \ - if vector_map[str(data_point.id)][property_name] is not None \ - else None \ + vectorized_values[vector_map[str(data_point.id)][property_name]] + if vector_map[str(data_point.id)][property_name] is not None + else None for property_name in DataPoint.get_embeddable_property_names(data_point) ], - ) for data_point in data_points + ) + for data_point in data_points ] for query in queries: @@ -144,17 +154,27 @@ async def create_vector_index(self, index_name: str, index_property_name: str): graph = self.driver.select_graph(self.graph_name) if not self.has_vector_index(graph, index_name, index_property_name): - graph.create_node_vector_index(index_name, index_property_name, dim = self.embedding_engine.get_vector_size()) + graph.create_node_vector_index( + index_name, index_property_name, dim=self.embedding_engine.get_vector_size() + ) def has_vector_index(self, graph, index_name: str, index_property_name: str) -> bool: try: indices = graph.list_indices() - return any([(index[0] == index_name and index_property_name in index[1]) for index in indices.result_set]) - except: + return any( + [ + (index[0] == index_name and index_property_name in index[1]) + for index in indices.result_set + ] + ) + except Error as e: + print(e) return False - async def index_data_points(self, index_name: str, index_property_name: str, data_points: list[DataPoint]): + async def index_data_points( + self, index_name: str, index_property_name: str, data_points: list[DataPoint] + ): pass async def add_node(self, node: DataPoint): @@ -183,11 +203,14 @@ async def has_edges(self, edges): """).strip() params = { - "edges": [{ - "from_node": str(edge[0]), - "to_node": str(edge[1]), - "relationship_name": edge[2], - } for edge in edges], + "edges": [ + { + "from_node": str(edge[0]), + "to_node": str(edge[1]), + "relationship_name": edge[2], + } + for edge in edges + ], } results = self.query(query, params).result_set @@ -196,7 +219,7 @@ async def has_edges(self, edges): async def retrieve(self, data_point_ids: list[UUID]): result = self.query( - f"MATCH (node) WHERE node.id IN $node_ids RETURN node", + "MATCH (node) WHERE node.id IN $node_ids RETURN node", { "node_ids": [str(data_point) for data_point in data_point_ids], }, @@ -224,19 +247,19 @@ async def get_connections(self, node_id: UUID) -> list: """ predecessors, successors = await asyncio.gather( - self.query(predecessors_query, dict(node_id = node_id)), - self.query(successors_query, dict(node_id = node_id)), + self.query(predecessors_query, dict(node_id=node_id)), + self.query(successors_query, dict(node_id=node_id)), ) connections = [] for neighbour in predecessors: neighbour = neighbour["relation"] - connections.append((neighbour[0], { "relationship_name": neighbour[1] }, neighbour[2])) + connections.append((neighbour[0], {"relationship_name": neighbour[1]}, neighbour[2])) for neighbour in successors: neighbour = neighbour["relation"] - connections.append((neighbour[0], { "relationship_name": neighbour[1] }, neighbour[2])) + connections.append((neighbour[0], {"relationship_name": neighbour[1]}, neighbour[2])) return connections @@ -279,12 +302,15 @@ async def batch_search( query_vectors = await self.embedding_engine.embed_text(query_texts) return await asyncio.gather( - *[self.search( - collection_name = collection_name, - query_vector = query_vector, - limit = limit, - with_vector = with_vectors, - ) for query_vector in query_vectors] + *[ + self.search( + collection_name=collection_name, + query_vector=query_vector, + limit=limit, + with_vector=with_vectors, + ) + for query_vector in query_vectors + ] ) async def get_graph_data(self): @@ -292,28 +318,34 @@ async def get_graph_data(self): result = self.query(query) - nodes = [( - record[2]["id"], - record[2], - ) for record in result.result_set] + nodes = [ + ( + record[2]["id"], + record[2], + ) + for record in result.result_set + ] query = """ MATCH (n)-[r]->(m) RETURN ID(n) AS source, ID(m) AS target, TYPE(r) AS type, properties(r) AS properties """ result = self.query(query) - edges = [( - record[3]["source_node_id"], - record[3]["target_node_id"], - record[2], - record[3], - ) for record in result.result_set] + edges = [ + ( + record[3]["source_node_id"], + record[3]["target_node_id"], + record[2], + record[3], + ) + for record in result.result_set + ] return (nodes, edges) async def delete_data_points(self, collection_name: str, data_point_ids: list[UUID]): return self.query( - f"MATCH (node) WHERE node.id IN $node_ids DETACH DELETE node", + "MATCH (node) WHERE node.id IN $node_ids DETACH DELETE node", { "node_ids": [str(data_point) for data_point in data_point_ids], }, diff --git a/cognee/infrastructure/databases/relational/ModelBase.py b/cognee/infrastructure/databases/relational/ModelBase.py index f75ec448b..fa2b68a5d 100644 --- a/cognee/infrastructure/databases/relational/ModelBase.py +++ b/cognee/infrastructure/databases/relational/ModelBase.py @@ -1,4 +1,5 @@ from sqlalchemy.orm import DeclarativeBase + class Base(DeclarativeBase): pass diff --git a/cognee/infrastructure/databases/relational/config.py b/cognee/infrastructure/databases/relational/config.py index ef8dca608..5ca121593 100644 --- a/cognee/infrastructure/databases/relational/config.py +++ b/cognee/infrastructure/databases/relational/config.py @@ -4,16 +4,17 @@ from pydantic_settings import BaseSettings, SettingsConfigDict from cognee.root_dir import get_absolute_path + class RelationalConfig(BaseSettings): - db_path: str = os.path.join(get_absolute_path(".cognee_system"), "databases") - db_name: str = "cognee_db" - db_host: Union[str, None] = None # "localhost" - db_port: Union[str, None] = None # "5432" - db_username: Union[str, None] = None # "cognee" - db_password: Union[str, None] = None # "cognee" + db_path: str = os.path.join(get_absolute_path(".cognee_system"), "databases") + db_name: str = "cognee_db" + db_host: Union[str, None] = None # "localhost" + db_port: Union[str, None] = None # "5432" + db_username: Union[str, None] = None # "cognee" + db_password: Union[str, None] = None # "cognee" db_provider: str = "sqlite" - model_config = SettingsConfigDict(env_file = ".env", extra = "allow") + model_config = SettingsConfigDict(env_file=".env", extra="allow") def to_dict(self) -> dict: return { @@ -26,6 +27,7 @@ def to_dict(self) -> dict: "db_provider": self.db_provider, } + @lru_cache def get_relational_config(): return RelationalConfig() diff --git a/cognee/infrastructure/databases/relational/create_db_and_tables.py b/cognee/infrastructure/databases/relational/create_db_and_tables.py index 02ff680df..4947680f5 100644 --- a/cognee/infrastructure/databases/relational/create_db_and_tables.py +++ b/cognee/infrastructure/databases/relational/create_db_and_tables.py @@ -2,6 +2,7 @@ from .ModelBase import Base from .get_relational_engine import get_relational_engine, get_relational_config + async def create_db_and_tables(): relational_config = get_relational_config() relational_engine = get_relational_engine() diff --git a/cognee/infrastructure/databases/relational/create_relational_engine.py b/cognee/infrastructure/databases/relational/create_relational_engine.py index 08cf171d5..13a1edc23 100644 --- a/cognee/infrastructure/databases/relational/create_relational_engine.py +++ b/cognee/infrastructure/databases/relational/create_relational_engine.py @@ -1,5 +1,6 @@ from .sqlalchemy.SqlAlchemyAdapter import SQLAlchemyAdapter + def create_relational_engine( db_path: str, db_name: str, @@ -13,6 +14,8 @@ def create_relational_engine( connection_string = f"sqlite+aiosqlite:///{db_path}/{db_name}" if db_provider == "postgres": - connection_string = f"postgresql+asyncpg://{db_username}:{db_password}@{db_host}:{db_port}/{db_name}" + connection_string = ( + f"postgresql+asyncpg://{db_username}:{db_password}@{db_host}:{db_port}/{db_name}" + ) return SQLAlchemyAdapter(connection_string) diff --git a/cognee/infrastructure/databases/relational/get_relational_engine.py b/cognee/infrastructure/databases/relational/get_relational_engine.py index d035f5baf..44aa7213b 100644 --- a/cognee/infrastructure/databases/relational/get_relational_engine.py +++ b/cognee/infrastructure/databases/relational/get_relational_engine.py @@ -3,8 +3,9 @@ from .config import get_relational_config from .create_relational_engine import create_relational_engine + # @lru_cache def get_relational_engine(): relational_config = get_relational_config() - return create_relational_engine(**relational_config.to_dict()) \ No newline at end of file + return create_relational_engine(**relational_config.to_dict()) diff --git a/cognee/infrastructure/databases/relational/sqlalchemy/SqlAlchemyAdapter.py b/cognee/infrastructure/databases/relational/sqlalchemy/SqlAlchemyAdapter.py index c197efc72..6c3c5029d 100644 --- a/cognee/infrastructure/databases/relational/sqlalchemy/SqlAlchemyAdapter.py +++ b/cognee/infrastructure/databases/relational/sqlalchemy/SqlAlchemyAdapter.py @@ -1,6 +1,6 @@ import os from os import path -import logging +import logging from uuid import UUID from typing import Optional from typing import AsyncGenerator, List @@ -18,7 +18,8 @@ logger = logging.getLogger(__name__) -class SQLAlchemyAdapter(): + +class SQLAlchemyAdapter: def __init__(self, connection_string: str): self.db_path: str = None self.db_uri: str = connection_string @@ -58,17 +59,23 @@ async def create_table(self, schema_name: str, table_name: str, table_config: li fields_query_parts = [f"{item['name']} {item['type']}" for item in table_config] async with self.engine.begin() as connection: await connection.execute(text(f"CREATE SCHEMA IF NOT EXISTS {schema_name};")) - await connection.execute(text(f"CREATE TABLE IF NOT EXISTS {schema_name}.{table_name} ({', '.join(fields_query_parts)});")) + await connection.execute( + text( + f"CREATE TABLE IF NOT EXISTS {schema_name}.{table_name} ({', '.join(fields_query_parts)});" + ) + ) await connection.close() - async def delete_table(self, table_name: str, schema_name: Optional[str] = "public"): + async def delete_table(self, table_name: str, schema_name: Optional[str] = "public"): async with self.engine.begin() as connection: if self.engine.dialect.name == "sqlite": # SQLite doesn’t support schema namespaces and the CASCADE keyword. # However, foreign key constraint can be defined with ON DELETE CASCADE during table creation. await connection.execute(text(f"DROP TABLE IF EXISTS {table_name};")) else: - await connection.execute(text(f"DROP TABLE IF EXISTS {schema_name}.{table_name} CASCADE;")) + await connection.execute( + text(f"DROP TABLE IF EXISTS {schema_name}.{table_name} CASCADE;") + ) async def insert_data(self, schema_name: str, table_name: str, data: list[dict]): columns = ", ".join(data[0].keys()) @@ -94,7 +101,9 @@ async def get_schema_list(self) -> List[str]: return [schema[0] for schema in result.fetchall()] return [] - async def delete_entity_by_id(self, table_name: str, data_id: UUID, schema_name: Optional[str] = "public"): + async def delete_entity_by_id( + self, table_name: str, data_id: UUID, schema_name: Optional[str] = "public" + ): """ Delete entity in given table based on id. Table must have an id Column. """ @@ -114,7 +123,6 @@ async def delete_entity_by_id(self, table_name: str, data_id: UUID, schema_name: await session.execute(TableModel.delete().where(TableModel.c.id == data_id)) await session.commit() - async def delete_data_entity(self, data_id: UUID): """ Delete data and local files related to data if there are no references to it anymore. @@ -131,14 +139,19 @@ async def delete_data_entity(self, data_id: UUID): raise EntityNotFoundError(message=f"Entity not found: {str(e)}") # Check if other data objects point to the same raw data location - raw_data_location_entities = (await session.execute( - select(Data.raw_data_location).where(Data.raw_data_location == data_entity.raw_data_location))).all() + raw_data_location_entities = ( + await session.execute( + select(Data.raw_data_location).where( + Data.raw_data_location == data_entity.raw_data_location + ) + ) + ).all() # Don't delete local file unless this is the only reference to the file in the database if len(raw_data_location_entities) == 1: - # delete local file only if it's created by cognee from cognee.base_config import get_base_config + config = get_base_config() if config.data_root_directory in raw_data_location_entities[0].raw_data_location: @@ -198,15 +211,18 @@ async def get_table_names(self) -> List[str]: metadata.clear() return table_names - async def get_data(self, table_name: str, filters: dict = None): async with self.engine.begin() as connection: query = f"SELECT * FROM {table_name}" if filters: - filter_conditions = " AND ".join([ - f"{key} IN ({', '.join([f':{key}{i}' for i in range(len(value))])})" if isinstance(value, list) - else f"{key} = :{key}" for key, value in filters.items() - ]) + filter_conditions = " AND ".join( + [ + f"{key} IN ({', '.join([f':{key}{i}' for i in range(len(value))])})" + if isinstance(value, list) + else f"{key} = :{key}" + for key, value in filters.items() + ] + ) query += f" WHERE {filter_conditions};" query = text(query) results = await connection.execute(query, filters) @@ -252,7 +268,6 @@ async def drop_tables(self): except Exception as e: print(f"Error dropping database tables: {e}") - async def create_database(self): if self.engine.dialect.name == "sqlite": from cognee.infrastructure.files.storage import LocalStorage @@ -264,7 +279,6 @@ async def create_database(self): if len(Base.metadata.tables.keys()) > 0: await connection.run_sync(Base.metadata.create_all) - async def delete_database(self): try: if self.engine.dialect.name == "sqlite": @@ -281,7 +295,9 @@ async def delete_database(self): # Load the schema information into the MetaData object await connection.run_sync(metadata.reflect, schema=schema_name) for table in metadata.sorted_tables: - drop_table_query = text(f"DROP TABLE IF EXISTS {schema_name}.{table.name} CASCADE") + drop_table_query = text( + f"DROP TABLE IF EXISTS {schema_name}.{table.name} CASCADE" + ) await connection.execute(drop_table_query) metadata.clear() except Exception as e: diff --git a/cognee/infrastructure/databases/vector/config.py b/cognee/infrastructure/databases/vector/config.py index 846bc5842..f2c180446 100644 --- a/cognee/infrastructure/databases/vector/config.py +++ b/cognee/infrastructure/databases/vector/config.py @@ -3,16 +3,16 @@ from pydantic_settings import BaseSettings, SettingsConfigDict from cognee.root_dir import get_absolute_path + class VectorConfig(BaseSettings): vector_db_url: str = os.path.join( - os.path.join(get_absolute_path(".cognee_system"), "databases"), - "cognee.lancedb" + os.path.join(get_absolute_path(".cognee_system"), "databases"), "cognee.lancedb" ) vector_db_port: int = 1234 vector_db_key: str = "" vector_db_provider: str = "lancedb" - model_config = SettingsConfigDict(env_file = ".env", extra = "allow") + model_config = SettingsConfigDict(env_file=".env", extra="allow") def to_dict(self) -> dict: return { @@ -22,6 +22,7 @@ def to_dict(self) -> dict: "vector_db_provider": self.vector_db_provider, } + @lru_cache def get_vectordb_config(): return VectorConfig() diff --git a/cognee/infrastructure/databases/vector/create_vector_engine.py b/cognee/infrastructure/databases/vector/create_vector_engine.py index 32db48930..e61c272e1 100644 --- a/cognee/infrastructure/databases/vector/create_vector_engine.py +++ b/cognee/infrastructure/databases/vector/create_vector_engine.py @@ -16,9 +16,7 @@ def create_vector_engine(config: VectorConfig, embedding_engine): raise EnvironmentError("Missing requred Weaviate credentials!") return WeaviateAdapter( - config["vector_db_url"], - config["vector_db_key"], - embedding_engine=embedding_engine + config["vector_db_url"], config["vector_db_key"], embedding_engine=embedding_engine ) elif config["vector_db_provider"] == "qdrant": @@ -30,10 +28,10 @@ def create_vector_engine(config: VectorConfig, embedding_engine): return QDrantAdapter( url=config["vector_db_url"], api_key=config["vector_db_key"], - embedding_engine=embedding_engine + embedding_engine=embedding_engine, ) - elif config['vector_db_provider'] == 'milvus': + elif config["vector_db_provider"] == "milvus": from .milvus.MilvusAdapter import MilvusAdapter if not config["vector_db_url"]: @@ -41,11 +39,10 @@ def create_vector_engine(config: VectorConfig, embedding_engine): return MilvusAdapter( url=config["vector_db_url"], - api_key=config['vector_db_key'], - embedding_engine=embedding_engine + api_key=config["vector_db_key"], + embedding_engine=embedding_engine, ) - elif config["vector_db_provider"] == "pgvector": from cognee.infrastructure.databases.relational import get_relational_config diff --git a/cognee/infrastructure/databases/vector/embeddings/EmbeddingEngine.py b/cognee/infrastructure/databases/vector/embeddings/EmbeddingEngine.py index bcb99fb07..ea07ff4e2 100644 --- a/cognee/infrastructure/databases/vector/embeddings/EmbeddingEngine.py +++ b/cognee/infrastructure/databases/vector/embeddings/EmbeddingEngine.py @@ -1,5 +1,6 @@ from typing import Protocol + class EmbeddingEngine(Protocol): async def embed_text(self, text: list[str]) -> list[list[float]]: raise NotImplementedError() diff --git a/cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py b/cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py index 93f59cc77..27061ce45 100644 --- a/cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py +++ b/cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py @@ -43,14 +43,12 @@ def __init__( async def embed_text(self, text: List[str]) -> List[List[float]]: async def exponential_backoff(attempt): - wait_time = min(10 * (2 ** attempt), 60) # Max 60 seconds + wait_time = min(10 * (2**attempt), 60) # Max 60 seconds await asyncio.sleep(wait_time) try: if self.mock: - response = { - "data": [{"embedding": [0.0] * self.dimensions} for _ in text] - } + response = {"data": [{"embedding": [0.0] * self.dimensions} for _ in text]} self.retry_count = 0 @@ -61,7 +59,7 @@ async def exponential_backoff(attempt): input=text, api_key=self.api_key, api_base=self.endpoint, - api_version=self.api_version + api_version=self.api_version, ) self.retry_count = 0 @@ -73,7 +71,7 @@ async def exponential_backoff(attempt): if len(text) == 1: parts = [text] else: - parts = [text[0:math.ceil(len(text) / 2)], text[math.ceil(len(text) / 2):]] + parts = [text[0 : math.ceil(len(text) / 2)], text[math.ceil(len(text) / 2) :]] parts_futures = [self.embed_text(part) for part in parts] embeddings = await asyncio.gather(*parts_futures) @@ -89,7 +87,7 @@ async def exponential_backoff(attempt): except litellm.exceptions.RateLimitError: if self.retry_count >= self.MAX_RETRIES: - raise Exception(f"Rate limit exceeded and no more retries left.") + raise Exception("Rate limit exceeded and no more retries left.") await exponential_backoff(self.retry_count) diff --git a/cognee/infrastructure/databases/vector/embeddings/config.py b/cognee/infrastructure/databases/vector/embeddings/config.py index ecfb37204..042c063f8 100644 --- a/cognee/infrastructure/databases/vector/embeddings/config.py +++ b/cognee/infrastructure/databases/vector/embeddings/config.py @@ -2,6 +2,7 @@ from functools import lru_cache from pydantic_settings import BaseSettings, SettingsConfigDict + class EmbeddingConfig(BaseSettings): embedding_model: Optional[str] = "text-embedding-3-large" embedding_dimensions: Optional[int] = 3072 @@ -9,7 +10,8 @@ class EmbeddingConfig(BaseSettings): embedding_api_key: Optional[str] = None embedding_api_version: Optional[str] = None - model_config = SettingsConfigDict(env_file = ".env", extra = "allow") + model_config = SettingsConfigDict(env_file=".env", extra="allow") + @lru_cache def get_embedding_config(): diff --git a/cognee/infrastructure/databases/vector/embeddings/get_embedding_engine.py b/cognee/infrastructure/databases/vector/embeddings/get_embedding_engine.py index d2582fbf0..6bfb4dd15 100644 --- a/cognee/infrastructure/databases/vector/embeddings/get_embedding_engine.py +++ b/cognee/infrastructure/databases/vector/embeddings/get_embedding_engine.py @@ -3,15 +3,16 @@ from .EmbeddingEngine import EmbeddingEngine from .LiteLLMEmbeddingEngine import LiteLLMEmbeddingEngine + def get_embedding_engine() -> EmbeddingEngine: config = get_embedding_config() llm_config = get_llm_config() return LiteLLMEmbeddingEngine( # If OpenAI API is used for embeddings, litellm needs only the api_key. - api_key = config.embedding_api_key or llm_config.llm_api_key, - endpoint = config.embedding_endpoint, - api_version = config.embedding_api_version, - model = config.embedding_model, - dimensions = config.embedding_dimensions, + api_key=config.embedding_api_key or llm_config.llm_api_key, + endpoint=config.embedding_endpoint, + api_version=config.embedding_api_version, + model=config.embedding_model, + dimensions=config.embedding_dimensions, ) diff --git a/cognee/infrastructure/databases/vector/get_vector_engine.py b/cognee/infrastructure/databases/vector/get_vector_engine.py index 079a8903f..4a3e81d1e 100644 --- a/cognee/infrastructure/databases/vector/get_vector_engine.py +++ b/cognee/infrastructure/databases/vector/get_vector_engine.py @@ -3,6 +3,7 @@ from .create_vector_engine import create_vector_engine from functools import lru_cache + @lru_cache def get_vector_engine(): return create_vector_engine(get_vectordb_config().to_dict(), get_embedding_engine()) diff --git a/cognee/infrastructure/databases/vector/lancedb/LanceDBAdapter.py b/cognee/infrastructure/databases/vector/lancedb/LanceDBAdapter.py index 1b3fc55c3..2caa8be1e 100644 --- a/cognee/infrastructure/databases/vector/lancedb/LanceDBAdapter.py +++ b/cognee/infrastructure/databases/vector/lancedb/LanceDBAdapter.py @@ -21,10 +21,8 @@ class IndexSchema(DataPoint): id: str text: str - _metadata: dict = { - "index_fields": ["text"], - "type": "IndexSchema" - } + _metadata: dict = {"index_fields": ["text"], "type": "IndexSchema"} + class LanceDBAdapter(VectorDBInterface): name = "LanceDB" @@ -32,7 +30,6 @@ class LanceDBAdapter(VectorDBInterface): api_key: str connection: lancedb.AsyncConnection = None - def __init__( self, url: Optional[str], @@ -45,7 +42,7 @@ def __init__( async def get_connection(self): if self.connection is None: - self.connection = await lancedb.connect_async(self.url, api_key = self.api_key) + self.connection = await lancedb.connect_async(self.url, api_key=self.api_key) return self.connection @@ -71,9 +68,9 @@ class LanceDataPoint(LanceModel): if not await self.has_collection(collection_name): connection = await self.get_connection() return await connection.create_table( - name = collection_name, - schema = LanceDataPoint, - exist_ok = True, + name=collection_name, + schema=LanceDataPoint, + exist_ok=True, ) async def create_data_points(self, collection_name: str, data_points: list[DataPoint]): @@ -108,21 +105,22 @@ def create_lance_data_point(data_point: DataPoint, vector: list[float]) -> Lance properties["id"] = str(properties["id"]) return LanceDataPoint[str, self.get_data_point_schema(type(data_point))]( - id = str(data_point.id), - vector = vector, - payload = properties, + id=str(data_point.id), + vector=vector, + payload=properties, ) lance_data_points = [ create_lance_data_point(data_point, data_vectors[data_point_index]) - for (data_point_index, data_point) in enumerate(data_points) + for (data_point_index, data_point) in enumerate(data_points) ] - await collection.merge_insert("id") \ - .when_matched_update_all() \ - .when_not_matched_insert_all() \ + await ( + collection.merge_insert("id") + .when_matched_update_all() + .when_not_matched_insert_all() .execute(lance_data_points) - + ) async def retrieve(self, collection_name: str, data_point_ids: list[str]): connection = await self.get_connection() @@ -133,17 +131,17 @@ async def retrieve(self, collection_name: str, data_point_ids: list[str]): else: results = await collection.query().where(f"id IN {tuple(data_point_ids)}").to_pandas() - return [ScoredResult( - id = UUID(result["id"]), - payload = result["payload"], - score = 0, - ) for result in results.to_dict("index").values()] + return [ + ScoredResult( + id=UUID(result["id"]), + payload=result["payload"], + score=0, + ) + for result in results.to_dict("index").values() + ] async def get_distance_from_collection_elements( - self, - collection_name: str, - query_text: str = None, - query_vector: List[float] = None + self, collection_name: str, query_text: str = None, query_vector: List[float] = None ): if query_text is None and query_vector is None: raise InvalidValueError(message="One of query_text or query_vector must be provided!") @@ -160,11 +158,14 @@ async def get_distance_from_collection_elements( normalized_values = normalize_distances(result_values) - return [ScoredResult( - id=UUID(result["id"]), - payload=result["payload"], - score=normalized_values[value_index], - ) for value_index, result in enumerate(result_values)] + return [ + ScoredResult( + id=UUID(result["id"]), + payload=result["payload"], + score=normalized_values[value_index], + ) + for value_index, result in enumerate(result_values) + ] async def search( self, @@ -173,7 +174,7 @@ async def search( query_vector: List[float] = None, limit: int = 5, with_vector: bool = False, - normalized: bool = True + normalized: bool = True, ): if query_text is None and query_vector is None: raise InvalidValueError(message="One of query_text or query_vector must be provided!") @@ -190,11 +191,14 @@ async def search( normalized_values = normalize_distances(result_values) - return [ScoredResult( - id = UUID(result["id"]), - payload = result["payload"], - score = normalized_values[value_index], - ) for value_index, result in enumerate(result_values)] + return [ + ScoredResult( + id=UUID(result["id"]), + payload=result["payload"], + score=normalized_values[value_index], + ) + for value_index, result in enumerate(result_values) + ] async def batch_search( self, @@ -206,12 +210,15 @@ async def batch_search( query_vectors = await self.embedding_engine.embed_text(query_texts) return await asyncio.gather( - *[self.search( - collection_name = collection_name, - query_vector = query_vector, - limit = limit, - with_vector = with_vectors, - ) for query_vector in query_vectors] + *[ + self.search( + collection_name=collection_name, + query_vector=query_vector, + limit=limit, + with_vector=with_vectors, + ) + for query_vector in query_vectors + ] ) async def delete_data_points(self, collection_name: str, data_point_ids: list[str]): @@ -224,26 +231,34 @@ async def delete_data_points(self, collection_name: str, data_point_ids: list[st return results async def create_vector_index(self, index_name: str, index_property_name: str): - await self.create_collection(f"{index_name}_{index_property_name}", payload_schema = IndexSchema) + await self.create_collection( + f"{index_name}_{index_property_name}", payload_schema=IndexSchema + ) - async def index_data_points(self, index_name: str, index_property_name: str, data_points: list[DataPoint]): - await self.create_data_points(f"{index_name}_{index_property_name}", [ - IndexSchema( - id = str(data_point.id), - text = getattr(data_point, data_point._metadata["index_fields"][0]), - ) for data_point in data_points - ]) + async def index_data_points( + self, index_name: str, index_property_name: str, data_points: list[DataPoint] + ): + await self.create_data_points( + f"{index_name}_{index_property_name}", + [ + IndexSchema( + id=str(data_point.id), + text=getattr(data_point, data_point._metadata["index_fields"][0]), + ) + for data_point in data_points + ], + ) async def prune(self): # Clean up the database if it was set up as temporary if self.url.startswith("/"): - LocalStorage.remove_all(self.url) # Remove the temporary directory and files inside + LocalStorage.remove_all(self.url) # Remove the temporary directory and files inside def get_data_point_schema(self, model_type): return copy_model( model_type, - include_fields = { + include_fields={ "id": (str, ...), }, - exclude_fields = ["_metadata"], - ) \ No newline at end of file + exclude_fields=["_metadata"], + ) diff --git a/cognee/infrastructure/databases/vector/milvus/MilvusAdapter.py b/cognee/infrastructure/databases/vector/milvus/MilvusAdapter.py index 0d4ea05d3..aecd4650d 100644 --- a/cognee/infrastructure/databases/vector/milvus/MilvusAdapter.py +++ b/cognee/infrastructure/databases/vector/milvus/MilvusAdapter.py @@ -17,10 +17,7 @@ class IndexSchema(DataPoint): text: str - _metadata: dict = { - "index_fields": ["text"], - "type": "IndexSchema" - } + _metadata: dict = {"index_fields": ["text"], "type": "IndexSchema"} class MilvusAdapter(VectorDBInterface): @@ -35,8 +32,9 @@ def __init__(self, url: str, api_key: Optional[str], embedding_engine: Embedding self.embedding_engine = embedding_engine - def get_milvus_client(self) -> "MilvusClient": + def get_milvus_client(self): from pymilvus import MilvusClient + if self.api_key: client = MilvusClient(uri=self.url, token=self.api_key) else: @@ -54,11 +52,12 @@ async def has_collection(self, collection_name: str) -> bool: return await future async def create_collection( - self, - collection_name: str, - payload_schema=None, + self, + collection_name: str, + payload_schema=None, ): from pymilvus import DataType, MilvusException + client = self.get_milvus_client() if client.has_collection(collection_name=collection_name): logger.info(f"Collection '{collection_name}' already exists.") @@ -74,34 +73,18 @@ async def create_collection( ) schema.add_field( - field_name="id", - datatype=DataType.VARCHAR, - is_primary=True, - max_length=36 + field_name="id", datatype=DataType.VARCHAR, is_primary=True, max_length=36 ) - schema.add_field( - field_name="vector", - datatype=DataType.FLOAT_VECTOR, - dim=dimension - ) + schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=dimension) - schema.add_field( - field_name="text", - datatype=DataType.VARCHAR, - max_length=60535 - ) + schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=60535) index_params = client.prepare_index_params() - index_params.add_index( - field_name="vector", - metric_type="COSINE" - ) + index_params.add_index(field_name="vector", metric_type="COSINE") client.create_collection( - collection_name=collection_name, - schema=schema, - index_params=index_params + collection_name=collection_name, schema=schema, index_params=index_params ) client.load_collection(collection_name) @@ -112,12 +95,9 @@ async def create_collection( logger.error(f"Error creating collection '{collection_name}': {str(e)}") raise e - async def create_data_points( - self, - collection_name: str, - data_points: List[DataPoint] - ): + async def create_data_points(self, collection_name: str, data_points: List[DataPoint]): from pymilvus import MilvusException + client = self.get_milvus_client() data_vectors = await self.embed_data( [data_point.get_embeddable_data(data_point) for data_point in data_points] @@ -133,22 +113,23 @@ async def create_data_points( ] try: - result = client.insert( - collection_name=collection_name, - data=insert_data - ) + result = client.insert(collection_name=collection_name, data=insert_data) logger.info( f"Inserted {result.get('insert_count', 0)} data points into collection '{collection_name}'." ) return result except MilvusException as e: - logger.error(f"Error inserting data points into collection '{collection_name}': {str(e)}") + logger.error( + f"Error inserting data points into collection '{collection_name}': {str(e)}" + ) raise e async def create_vector_index(self, index_name: str, index_property_name: str): await self.create_collection(f"{index_name}_{index_property_name}") - async def index_data_points(self, index_name: str, index_property_name: str, data_points: List[DataPoint]): + async def index_data_points( + self, index_name: str, index_property_name: str, data_points: List[DataPoint] + ): formatted_data_points = [ IndexSchema( id=data_point.id, @@ -161,6 +142,7 @@ async def index_data_points(self, index_name: str, index_property_name: str, dat async def retrieve(self, collection_name: str, data_point_ids: list[str]): from pymilvus import MilvusException + client = self.get_milvus_client() try: filter_expression = f"""id in [{", ".join(f'"{id}"' for id in data_point_ids)}]""" @@ -172,18 +154,21 @@ async def retrieve(self, collection_name: str, data_point_ids: list[str]): ) return results except MilvusException as e: - logger.error(f"Error retrieving data points from collection '{collection_name}': {str(e)}") + logger.error( + f"Error retrieving data points from collection '{collection_name}': {str(e)}" + ) raise e async def search( - self, - collection_name: str, - query_text: Optional[str] = None, - query_vector: Optional[List[float]] = None, - limit: int = 5, - with_vector: bool = False, + self, + collection_name: str, + query_text: Optional[str] = None, + query_vector: Optional[List[float]] = None, + limit: int = 5, + with_vector: bool = False, ): from pymilvus import MilvusException + client = self.get_milvus_client() if query_text is None and query_vector is None: raise ValueError("One of query_text or query_vector must be provided!") @@ -218,32 +203,40 @@ async def search( logger.error(f"Error during search in collection '{collection_name}': {str(e)}") raise e - async def batch_search(self, collection_name: str, query_texts: List[str], limit: int, with_vectors: bool = False): + async def batch_search( + self, collection_name: str, query_texts: List[str], limit: int, with_vectors: bool = False + ): query_vectors = await self.embed_data(query_texts) return await asyncio.gather( - *[self.search(collection_name=collection_name, - query_vector=query_vector, - limit=limit, - with_vector=with_vectors, - ) for query_vector in query_vectors] + *[ + self.search( + collection_name=collection_name, + query_vector=query_vector, + limit=limit, + with_vector=with_vectors, + ) + for query_vector in query_vectors + ] ) async def delete_data_points(self, collection_name: str, data_point_ids: list[str]): from pymilvus import MilvusException + client = self.get_milvus_client() try: filter_expression = f"""id in [{", ".join(f'"{id}"' for id in data_point_ids)}]""" - delete_result = client.delete( - collection_name=collection_name, - filter=filter_expression - ) + delete_result = client.delete(collection_name=collection_name, filter=filter_expression) - logger.info(f"Deleted data points with IDs {data_point_ids} from collection '{collection_name}'.") + logger.info( + f"Deleted data points with IDs {data_point_ids} from collection '{collection_name}'." + ) return delete_result except MilvusException as e: - logger.error(f"Error deleting data points from collection '{collection_name}': {str(e)}") + logger.error( + f"Error deleting data points from collection '{collection_name}': {str(e)}" + ) raise e async def prune(self): diff --git a/cognee/infrastructure/databases/vector/models/CollectionConfig.py b/cognee/infrastructure/databases/vector/models/CollectionConfig.py index b46bdabfc..b279a6be2 100644 --- a/cognee/infrastructure/databases/vector/models/CollectionConfig.py +++ b/cognee/infrastructure/databases/vector/models/CollectionConfig.py @@ -1,5 +1,6 @@ from pydantic import BaseModel from .VectorConfig import VectorConfig + class CollectionConfig(BaseModel): vector_config: VectorConfig diff --git a/cognee/infrastructure/databases/vector/models/ScoredResult.py b/cognee/infrastructure/databases/vector/models/ScoredResult.py index f9d8bec77..8b6f00952 100644 --- a/cognee/infrastructure/databases/vector/models/ScoredResult.py +++ b/cognee/infrastructure/databases/vector/models/ScoredResult.py @@ -2,7 +2,8 @@ from uuid import UUID from pydantic import BaseModel + class ScoredResult(BaseModel): id: UUID - score: float # Lower score is better + score: float # Lower score is better payload: Dict[str, Any] diff --git a/cognee/infrastructure/databases/vector/models/VectorConfig.py b/cognee/infrastructure/databases/vector/models/VectorConfig.py index 2a968a997..fb89d90b5 100644 --- a/cognee/infrastructure/databases/vector/models/VectorConfig.py +++ b/cognee/infrastructure/databases/vector/models/VectorConfig.py @@ -1,6 +1,7 @@ from typing import Literal from pydantic import BaseModel + class VectorConfig(BaseModel): - distance: Literal['Cosine', 'Dot'] + distance: Literal["Cosine", "Dot"] size: int diff --git a/cognee/infrastructure/databases/vector/pgvector/PGVectorAdapter.py b/cognee/infrastructure/databases/vector/pgvector/PGVectorAdapter.py index 3f0565253..df22e8f18 100644 --- a/cognee/infrastructure/databases/vector/pgvector/PGVectorAdapter.py +++ b/cognee/infrastructure/databases/vector/pgvector/PGVectorAdapter.py @@ -22,13 +22,10 @@ class IndexSchema(DataPoint): text: str - _metadata: dict = { - "index_fields": ["text"], - "type": "IndexSchema" - } + _metadata: dict = {"index_fields": ["text"], "type": "IndexSchema"} -class PGVectorAdapter(SQLAlchemyAdapter, VectorDBInterface): +class PGVectorAdapter(SQLAlchemyAdapter, VectorDBInterface): def __init__( self, connection_string: str, @@ -44,6 +41,7 @@ def __init__( # Has to be imported at class level # Functions reading tables from database need to know what a Vector column type is from pgvector.sqlalchemy import Vector + self.Vector = Vector async def embed_data(self, data: list[str]) -> list[list[float]]: @@ -71,9 +69,7 @@ class PGVectorDataPoint(Base): __tablename__ = collection_name __table_args__ = {"extend_existing": True} # PGVector requires one column to be the primary key - primary_key: Mapped[int] = mapped_column( - primary_key=True, autoincrement=True - ) + primary_key: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) id: Mapped[data_point_types["id"]] payload = Column(JSON) vector = Column(self.Vector(vector_size)) @@ -89,14 +85,12 @@ def __init__(self, id, payload, vector): Base.metadata.create_all, tables=[PGVectorDataPoint.__table__] ) - async def create_data_points( - self, collection_name: str, data_points: List[DataPoint] - ): + async def create_data_points(self, collection_name: str, data_points: List[DataPoint]): data_point_types = get_type_hints(DataPoint) if not await self.has_collection(collection_name): await self.create_collection( - collection_name = collection_name, - payload_schema = type(data_points[0]), + collection_name=collection_name, + payload_schema=type(data_points[0]), ) data_vectors = await self.embed_data( @@ -109,9 +103,7 @@ class PGVectorDataPoint(Base): __tablename__ = collection_name __table_args__ = {"extend_existing": True} # PGVector requires one column to be the primary key - primary_key: Mapped[int] = mapped_column( - primary_key=True, autoincrement=True - ) + primary_key: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) id: Mapped[data_point_types["id"]] payload = Column(JSON) vector = Column(self.Vector(vector_size)) @@ -123,9 +115,9 @@ def __init__(self, id, payload, vector): pgvector_data_points = [ PGVectorDataPoint( - id = data_point.id, - vector = data_vectors[data_index], - payload = serialize_data(data_point.model_dump()), + id=data_point.id, + vector=data_vectors[data_index], + payload=serialize_data(data_point.model_dump()), ) for (data_index, data_point) in enumerate(data_points) ] @@ -137,13 +129,19 @@ def __init__(self, id, payload, vector): async def create_vector_index(self, index_name: str, index_property_name: str): await self.create_collection(f"{index_name}_{index_property_name}") - async def index_data_points(self, index_name: str, index_property_name: str, data_points: list[DataPoint]): - await self.create_data_points(f"{index_name}_{index_property_name}", [ - IndexSchema( - id = data_point.id, - text = DataPoint.get_embeddable_data(data_point), - ) for data_point in data_points - ]) + async def index_data_points( + self, index_name: str, index_property_name: str, data_points: list[DataPoint] + ): + await self.create_data_points( + f"{index_name}_{index_property_name}", + [ + IndexSchema( + id=data_point.id, + text=DataPoint.get_embeddable_data(data_point), + ) + for data_point in data_points + ], + ) async def get_table(self, collection_name: str) -> Table: """ @@ -171,20 +169,17 @@ async def retrieve(self, collection_name: str, data_point_ids: List[str]): results = results.all() return [ - ScoredResult( - id = UUID(result.id), - payload = result.payload, - score = 0 - ) for result in results + ScoredResult(id=UUID(result.id), payload=result.payload, score=0) + for result in results ] async def get_distance_from_collection_elements( - self, - collection_name: str, - query_text: str = None, - query_vector: List[float] = None, - with_vector: bool = False - )-> List[ScoredResult]: + self, + collection_name: str, + query_text: str = None, + query_vector: List[float] = None, + with_vector: bool = False, + ) -> List[ScoredResult]: if query_text is None and query_vector is None: raise ValueError("One of query_text or query_vector must be provided!") @@ -200,11 +195,8 @@ async def get_distance_from_collection_elements( closest_items = await session.execute( select( PGVectorDataPoint, - PGVectorDataPoint.c.vector.cosine_distance(query_vector).label( - "similarity" - ), - ) - .order_by("similarity") + PGVectorDataPoint.c.vector.cosine_distance(query_vector).label("similarity"), + ).order_by("similarity") ) vector_list = [] @@ -216,11 +208,8 @@ async def get_distance_from_collection_elements( # Create and return ScoredResult objects return [ - ScoredResult( - id = UUID(str(row.id)), - payload = row.payload, - score = row.similarity - ) for row in vector_list + ScoredResult(id=UUID(str(row.id)), payload=row.payload, score=row.similarity) + for row in vector_list ] async def search( @@ -248,9 +237,7 @@ async def search( closest_items = await session.execute( select( PGVectorDataPoint, - PGVectorDataPoint.c.vector.cosine_distance(query_vector).label( - "similarity" - ), + PGVectorDataPoint.c.vector.cosine_distance(query_vector).label("similarity"), ) .order_by("similarity") .limit(limit) @@ -265,11 +252,8 @@ async def search( # Create and return ScoredResult objects return [ - ScoredResult( - id = UUID(str(row.id)), - payload = row.payload, - score = row.similarity - ) for row in vector_list + ScoredResult(id=UUID(str(row.id)), payload=row.payload, score=row.similarity) + for row in vector_list ] async def batch_search( diff --git a/cognee/infrastructure/databases/vector/pgvector/__init__.py b/cognee/infrastructure/databases/vector/pgvector/__init__.py index 130246a31..2f8f08620 100644 --- a/cognee/infrastructure/databases/vector/pgvector/__init__.py +++ b/cognee/infrastructure/databases/vector/pgvector/__init__.py @@ -1,2 +1,2 @@ from .PGVectorAdapter import PGVectorAdapter -from .create_db_and_tables import create_db_and_tables \ No newline at end of file +from .create_db_and_tables import create_db_and_tables diff --git a/cognee/infrastructure/databases/vector/pgvector/create_db_and_tables.py b/cognee/infrastructure/databases/vector/pgvector/create_db_and_tables.py index 2f4c9cf3f..881c1e72f 100644 --- a/cognee/infrastructure/databases/vector/pgvector/create_db_and_tables.py +++ b/cognee/infrastructure/databases/vector/pgvector/create_db_and_tables.py @@ -9,4 +9,3 @@ async def create_db_and_tables(): if vector_config.vector_db_provider == "pgvector": async with vector_engine.engine.begin() as connection: await connection.execute(text("CREATE EXTENSION IF NOT EXISTS vector;")) - diff --git a/cognee/infrastructure/databases/vector/pgvector/serialize_data.py b/cognee/infrastructure/databases/vector/pgvector/serialize_data.py index cdba1e928..a61a2682f 100644 --- a/cognee/infrastructure/databases/vector/pgvector/serialize_data.py +++ b/cognee/infrastructure/databases/vector/pgvector/serialize_data.py @@ -1,6 +1,7 @@ from datetime import datetime from uuid import UUID + def serialize_data(data): """Recursively convert datetime objects in dictionaries/lists to ISO format.""" if isinstance(data, dict): @@ -12,4 +13,4 @@ def serialize_data(data): elif isinstance(data, UUID): return str(data) else: - return data \ No newline at end of file + return data diff --git a/cognee/infrastructure/databases/vector/pinecone/adapter.py b/cognee/infrastructure/databases/vector/pinecone/adapter.py index 2a8e628f9..00dd7daf1 100644 --- a/cognee/infrastructure/databases/vector/pinecone/adapter.py +++ b/cognee/infrastructure/databases/vector/pinecone/adapter.py @@ -1,8 +1,8 @@ -class PineconeVectorDB(VectorDB): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.init_pinecone(self.index_name) - - def init_pinecone(self, index_name): - # Pinecone initialization logic - pass +# class PineconeVectorDB(VectorDB): +# def __init__(self, *args, **kwargs): +# super().__init__(*args, **kwargs) +# self.init_pinecone(self.index_name) +# +# def init_pinecone(self, index_name): +# # Pinecone initialization logic +# pass diff --git a/cognee/infrastructure/databases/vector/qdrant/QDrantAdapter.py b/cognee/infrastructure/databases/vector/qdrant/QDrantAdapter.py index b63139bc5..64e64e269 100644 --- a/cognee/infrastructure/databases/vector/qdrant/QDrantAdapter.py +++ b/cognee/infrastructure/databases/vector/qdrant/QDrantAdapter.py @@ -5,8 +5,7 @@ from qdrant_client import AsyncQdrantClient, models from cognee.exceptions import InvalidValueError -from cognee.infrastructure.databases.vector.models.ScoredResult import \ - ScoredResult +from cognee.infrastructure.databases.vector.models.ScoredResult import ScoredResult from cognee.infrastructure.engine import DataPoint from ..embeddings.EmbeddingEngine import EmbeddingEngine @@ -14,13 +13,12 @@ logger = logging.getLogger("QDrantAdapter") + class IndexSchema(DataPoint): text: str - _metadata: dict = { - "index_fields": ["text"], - "type": "IndexSchema" - } + _metadata: dict = {"index_fields": ["text"], "type": "IndexSchema"} + # class CollectionConfig(BaseModel, extra = "forbid"): # vector_config: Dict[str, models.VectorParams] = Field(..., description="Vectors configuration" ) @@ -28,28 +26,32 @@ class IndexSchema(DataPoint): # optimizers_config: Optional[models.OptimizersConfig] = Field(default = None, description="Optimizers configuration") # quantization_config: Optional[models.QuantizationConfig] = Field(default = None, description="Quantization configuration") + def create_hnsw_config(hnsw_config: Dict): if hnsw_config is not None: return models.HnswConfig() return None + def create_optimizers_config(optimizers_config: Dict): if optimizers_config is not None: return models.OptimizersConfig() return None + def create_quantization_config(quantization_config: Dict): if quantization_config is not None: return models.QuantizationConfig() return None + class QDrantAdapter(VectorDBInterface): name = "Qdrant" url: str = None api_key: str = None qdrant_path: str = None - def __init__(self, url, api_key, embedding_engine: EmbeddingEngine, qdrant_path = None): + def __init__(self, url, api_key, embedding_engine: EmbeddingEngine, qdrant_path=None): self.embedding_engine = embedding_engine if qdrant_path is not None: @@ -60,19 +62,11 @@ def __init__(self, url, api_key, embedding_engine: EmbeddingEngine, qdrant_path def get_qdrant_client(self) -> AsyncQdrantClient: if self.qdrant_path is not None: - return AsyncQdrantClient( - path = self.qdrant_path, port=6333 - ) + return AsyncQdrantClient(path=self.qdrant_path, port=6333) elif self.url is not None: - return AsyncQdrantClient( - url = self.url, - api_key = self.api_key, - port = 6333 - ) + return AsyncQdrantClient(url=self.url, api_key=self.api_key, port=6333) - return AsyncQdrantClient( - location = ":memory:" - ) + return AsyncQdrantClient(location=":memory:") async def embed_data(self, data: List[str]) -> List[float]: return await self.embedding_engine.embed_text(data) @@ -84,21 +78,20 @@ async def has_collection(self, collection_name: str) -> bool: return result async def create_collection( - self, - collection_name: str, - payload_schema = None, + self, + collection_name: str, + payload_schema=None, ): client = self.get_qdrant_client() if not await client.collection_exists(collection_name): await client.create_collection( - collection_name = collection_name, - vectors_config = { + collection_name=collection_name, + vectors_config={ "text": models.VectorParams( - size = self.embedding_engine.get_vector_size(), - distance = "Cosine" + size=self.embedding_engine.get_vector_size(), distance="Cosine" ) - } + }, ) await client.close() @@ -106,26 +99,21 @@ async def create_collection( async def create_data_points(self, collection_name: str, data_points: List[DataPoint]): client = self.get_qdrant_client() - data_vectors = await self.embed_data([ - DataPoint.get_embeddable_data(data_point) for data_point in data_points - ]) + data_vectors = await self.embed_data( + [DataPoint.get_embeddable_data(data_point) for data_point in data_points] + ) def convert_to_qdrant_point(data_point: DataPoint): return models.PointStruct( - id = str(data_point.id), - payload = data_point.model_dump(), - vector = { - "text": data_vectors[data_points.index(data_point)] - } + id=str(data_point.id), + payload=data_point.model_dump(), + vector={"text": data_vectors[data_points.index(data_point)]}, ) points = [convert_to_qdrant_point(point) for point in data_points] try: - client.upload_points( - collection_name = collection_name, - points = points - ) + client.upload_points(collection_name=collection_name, points=points) except Exception as error: logger.error("Error uploading data points to Qdrant: %s", str(error)) raise error @@ -135,53 +123,61 @@ def convert_to_qdrant_point(data_point: DataPoint): async def create_vector_index(self, index_name: str, index_property_name: str): await self.create_collection(f"{index_name}_{index_property_name}") - async def index_data_points(self, index_name: str, index_property_name: str, data_points: list[DataPoint]): - await self.create_data_points(f"{index_name}_{index_property_name}", [ - IndexSchema( - id = data_point.id, - text = getattr(data_point, data_point._metadata["index_fields"][0]), - ) for data_point in data_points - ]) + async def index_data_points( + self, index_name: str, index_property_name: str, data_points: list[DataPoint] + ): + await self.create_data_points( + f"{index_name}_{index_property_name}", + [ + IndexSchema( + id=data_point.id, + text=getattr(data_point, data_point._metadata["index_fields"][0]), + ) + for data_point in data_points + ], + ) async def retrieve(self, collection_name: str, data_point_ids: list[str]): client = self.get_qdrant_client() - results = await client.retrieve(collection_name, data_point_ids, with_payload = True) + results = await client.retrieve(collection_name, data_point_ids, with_payload=True) await client.close() return results async def get_distance_from_collection_elements( - self, - collection_name: str, - query_text: str = None, - query_vector: List[float] = None, - with_vector: bool = False + self, + collection_name: str, + query_text: str = None, + query_vector: List[float] = None, + with_vector: bool = False, ) -> List[ScoredResult]: - if query_text is None and query_vector is None: raise ValueError("One of query_text or query_vector must be provided!") client = self.get_qdrant_client() results = await client.search( - collection_name = collection_name, - query_vector = models.NamedVector( - name = "text", - vector = query_vector if query_vector is not None else (await self.embed_data([query_text]))[0], + collection_name=collection_name, + query_vector=models.NamedVector( + name="text", + vector=query_vector + if query_vector is not None + else (await self.embed_data([query_text]))[0], ), - with_vectors = with_vector + with_vectors=with_vector, ) await client.close() return [ ScoredResult( - id = UUID(result.id), - payload = { + id=UUID(result.id), + payload={ **result.payload, "id": UUID(result.id), }, - score = 1 - result.score, - ) for result in results + score=1 - result.score, + ) + for result in results ] async def search( @@ -190,7 +186,7 @@ async def search( query_text: Optional[str] = None, query_vector: Optional[List[float]] = None, limit: int = 5, - with_vector: bool = False + with_vector: bool = False, ): if query_text is None and query_vector is None: raise InvalidValueError(message="One of query_text or query_vector must be provided!") @@ -198,30 +194,38 @@ async def search( client = self.get_qdrant_client() results = await client.search( - collection_name = collection_name, - query_vector = models.NamedVector( - name = "text", - vector = query_vector if query_vector is not None else (await self.embed_data([query_text]))[0], + collection_name=collection_name, + query_vector=models.NamedVector( + name="text", + vector=query_vector + if query_vector is not None + else (await self.embed_data([query_text]))[0], ), - limit = limit, - with_vectors = with_vector + limit=limit, + with_vectors=with_vector, ) await client.close() return [ ScoredResult( - id = UUID(result.id), - payload = { + id=UUID(result.id), + payload={ **result.payload, "id": UUID(result.id), }, - score = 1 - result.score, - ) for result in results + score=1 - result.score, + ) + for result in results ] - - async def batch_search(self, collection_name: str, query_texts: List[str], limit: int = None, with_vectors: bool = False): + async def batch_search( + self, + collection_name: str, + query_texts: List[str], + limit: int = None, + with_vectors: bool = False, + ): """ Perform batch search in a Qdrant collection with dynamic search requests. @@ -240,22 +244,17 @@ async def batch_search(self, collection_name: str, query_texts: List[str], limit # Generate dynamic search requests based on the provided embeddings requests = [ models.SearchRequest( - vector = models.NamedVector( - name = "text", - vector = vector - ), - limit = limit, - with_vector = with_vectors - ) for vector in vectors + vector=models.NamedVector(name="text", vector=vector), + limit=limit, + with_vector=with_vectors, + ) + for vector in vectors ] client = self.get_qdrant_client() # Perform batch search with the dynamically generated requests - results = await client.search_batch( - collection_name = collection_name, - requests = requests - ) + results = await client.search_batch(collection_name=collection_name, requests=requests) await client.close() diff --git a/cognee/infrastructure/databases/vector/utils.py b/cognee/infrastructure/databases/vector/utils.py index d5a5897a3..9507207c0 100644 --- a/cognee/infrastructure/databases/vector/utils.py +++ b/cognee/infrastructure/databases/vector/utils.py @@ -1,5 +1,6 @@ from typing import List + def normalize_distances(result_values: List[dict]) -> List[float]: min_value = min(result["_distance"] for result in result_values) max_value = max(result["_distance"] for result in result_values) @@ -8,7 +9,8 @@ def normalize_distances(result_values: List[dict]) -> List[float]: # Avoid division by zero: Assign all normalized values to 0 (or any constant value like 1) normalized_values = [0 for _ in result_values] else: - normalized_values = [(result["_distance"] - min_value) / (max_value - min_value) for result in - result_values] + normalized_values = [ + (result["_distance"] - min_value) / (max_value - min_value) for result in result_values + ] return normalized_values diff --git a/cognee/infrastructure/databases/vector/vector_db_interface.py b/cognee/infrastructure/databases/vector/vector_db_interface.py index 457b92f07..939997fec 100644 --- a/cognee/infrastructure/databases/vector/vector_db_interface.py +++ b/cognee/infrastructure/databases/vector/vector_db_interface.py @@ -3,8 +3,10 @@ from cognee.infrastructure.engine import DataPoint from .models.PayloadSchema import PayloadSchema + class VectorDBInterface(Protocol): - """ Collections """ + """Collections""" + @abstractmethod async def has_collection(self, collection_name: str) -> bool: raise NotImplementedError @@ -14,24 +16,21 @@ async def create_collection( self, collection_name: str, payload_schema: Optional[PayloadSchema] = None, - ): raise NotImplementedError + ): + raise NotImplementedError """ Data points """ + @abstractmethod - async def create_data_points( - self, - collection_name: str, - data_points: List[DataPoint] - ): raise NotImplementedError + async def create_data_points(self, collection_name: str, data_points: List[DataPoint]): + raise NotImplementedError @abstractmethod - async def retrieve( - self, - collection_name: str, - data_point_ids: list[str] - ): raise NotImplementedError + async def retrieve(self, collection_name: str, data_point_ids: list[str]): + raise NotImplementedError """ Search """ + @abstractmethod async def search( self, @@ -39,25 +38,20 @@ async def search( query_text: Optional[str], query_vector: Optional[List[float]], limit: int, - with_vector: bool = False - - ): raise NotImplementedError + with_vector: bool = False, + ): + raise NotImplementedError @abstractmethod async def batch_search( - self, - collection_name: str, - query_texts: List[str], - limit: int, - with_vectors: bool = False - ): raise NotImplementedError + self, collection_name: str, query_texts: List[str], limit: int, with_vectors: bool = False + ): + raise NotImplementedError @abstractmethod - async def delete_data_points( - self, - collection_name: str, - data_point_ids: list[str] - ): raise NotImplementedError + async def delete_data_points(self, collection_name: str, data_point_ids: list[str]): + raise NotImplementedError @abstractmethod - async def prune(self): raise NotImplementedError + async def prune(self): + raise NotImplementedError diff --git a/cognee/infrastructure/databases/vector/weaviate_db/WeaviateAdapter.py b/cognee/infrastructure/databases/vector/weaviate_db/WeaviateAdapter.py index 31162b1b5..259370643 100644 --- a/cognee/infrastructure/databases/vector/weaviate_db/WeaviateAdapter.py +++ b/cognee/infrastructure/databases/vector/weaviate_db/WeaviateAdapter.py @@ -12,13 +12,12 @@ logger = logging.getLogger("WeaviateAdapter") + class IndexSchema(DataPoint): text: str - _metadata: dict = { - "index_fields": ["text"], - "type": "IndexSchema" - } + _metadata: dict = {"index_fields": ["text"], "type": "IndexSchema"} + class WeaviateAdapter(VectorDBInterface): name = "Weaviate" @@ -36,9 +35,9 @@ def __init__(self, url: str, api_key: str, embedding_engine: EmbeddingEngine): self.embedding_engine = embedding_engine self.client = weaviate.connect_to_wcs( - cluster_url = url, - auth_credentials = weaviate.auth.AuthApiKey(api_key), - additional_config = wvc.init.AdditionalConfig(timeout = wvc.init.Timeout(init=30)) + cluster_url=url, + auth_credentials=weaviate.auth.AuthApiKey(api_key), + additional_config=wvc.init.AdditionalConfig(timeout=wvc.init.Timeout(init=30)), ) async def embed_data(self, data: List[str]) -> List[float]: @@ -54,7 +53,7 @@ async def has_collection(self, collection_name: str) -> bool: async def create_collection( self, collection_name: str, - payload_schema = None, + payload_schema=None, ): import weaviate.classes.config as wvcc @@ -63,14 +62,12 @@ async def create_collection( if not self.client.collections.exists(collection_name): future.set_result( self.client.collections.create( - name = collection_name, - properties = [ + name=collection_name, + properties=[ wvcc.Property( - name = "text", - data_type = wvcc.DataType.TEXT, - skip_vectorization = True + name="text", data_type=wvcc.DataType.TEXT, skip_vectorization=True ) - ] + ], ) ) else: @@ -96,11 +93,7 @@ def convert_to_weaviate_data_points(data_point: DataPoint): properties["uuid"] = str(data_point.id) del properties["id"] - return DataObject( - uuid = data_point.id, - properties = properties, - vector = vector - ) + return DataObject(uuid=data_point.id, properties=properties, vector=vector) data_points = [convert_to_weaviate_data_points(data_point) for data_point in data_points] @@ -111,26 +104,26 @@ def convert_to_weaviate_data_points(data_point: DataPoint): with collection.batch.dynamic() as batch: for data_point in data_points: batch.add_object( - uuid = data_point.uuid, - vector = data_point.vector, - properties = data_point.properties, - references = data_point.references, + uuid=data_point.uuid, + vector=data_point.vector, + properties=data_point.properties, + references=data_point.references, ) else: data_point: DataObject = data_points[0] if collection.data.exists(data_point.uuid): return collection.data.update( - uuid = data_point.uuid, - vector = data_point.vector, - properties = data_point.properties, - references = data_point.references, + uuid=data_point.uuid, + vector=data_point.vector, + properties=data_point.properties, + references=data_point.references, ) else: return collection.data.insert( - uuid = data_point.uuid, - vector = data_point.vector, - properties = data_point.properties, - references = data_point.references, + uuid=data_point.uuid, + vector=data_point.vector, + properties=data_point.properties, + references=data_point.references, ) except Exception as error: logger.error("Error creating data points: %s", str(error)) @@ -139,20 +132,27 @@ def convert_to_weaviate_data_points(data_point: DataPoint): async def create_vector_index(self, index_name: str, index_property_name: str): await self.create_collection(f"{index_name}_{index_property_name}") - async def index_data_points(self, index_name: str, index_property_name: str, data_points: list[DataPoint]): - await self.create_data_points(f"{index_name}_{index_property_name}", [ - IndexSchema( - id = data_point.id, - text = DataPoint.get_embeddable_data(data_point), - ) for data_point in data_points - ]) + async def index_data_points( + self, index_name: str, index_property_name: str, data_points: list[DataPoint] + ): + await self.create_data_points( + f"{index_name}_{index_property_name}", + [ + IndexSchema( + id=data_point.id, + text=DataPoint.get_embeddable_data(data_point), + ) + for data_point in data_points + ], + ) async def retrieve(self, collection_name: str, data_point_ids: list[str]): from weaviate.classes.query import Filter + future = asyncio.Future() data_points = self.get_collection(collection_name).query.fetch_objects( - filters = Filter.by_id().contains_any(data_point_ids) + filters=Filter.by_id().contains_any(data_point_ids) ) for data_point in data_points.objects: @@ -165,11 +165,11 @@ async def retrieve(self, collection_name: str, data_point_ids: list[str]): return await future async def get_distance_from_collection_elements( - self, - collection_name: str, - query_text: str = None, - query_vector: List[float] = None, - with_vector: bool = False + self, + collection_name: str, + query_text: str = None, + query_vector: List[float] = None, + with_vector: bool = False, ) -> List[ScoredResult]: import weaviate.classes as wvc @@ -190,17 +190,18 @@ async def get_distance_from_collection_elements( ScoredResult( id=UUID(str(result.uuid)), payload=result.properties, - score=1 - float(result.metadata.score) - ) for result in search_result.objects + score=1 - float(result.metadata.score), + ) + for result in search_result.objects ] async def search( - self, - collection_name: str, - query_text: Optional[str] = None, - query_vector: Optional[List[float]] = None, - limit: int = None, - with_vector: bool = False + self, + collection_name: str, + query_text: Optional[str] = None, + query_vector: Optional[List[float]] = None, + limit: int = None, + with_vector: bool = False, ): import weaviate.classes as wvc @@ -211,33 +212,41 @@ async def search( query_vector = (await self.embed_data([query_text]))[0] search_result = self.get_collection(collection_name).query.hybrid( - query = None, - vector = query_vector, - limit = limit, - include_vector = with_vector, - return_metadata = wvc.query.MetadataQuery(score=True), + query=None, + vector=query_vector, + limit=limit, + include_vector=with_vector, + return_metadata=wvc.query.MetadataQuery(score=True), ) return [ ScoredResult( - id = UUID(str(result.uuid)), - payload = result.properties, - score = 1 - float(result.metadata.score) - ) for result in search_result.objects + id=UUID(str(result.uuid)), + payload=result.properties, + score=1 - float(result.metadata.score), + ) + for result in search_result.objects ] - async def batch_search(self, collection_name: str, query_texts: List[str], limit: int, with_vectors: bool = False): + async def batch_search( + self, collection_name: str, query_texts: List[str], limit: int, with_vectors: bool = False + ): def query_search(query_vector): - return self.search(collection_name, query_vector=query_vector, limit=limit, with_vector=with_vectors) + return self.search( + collection_name, query_vector=query_vector, limit=limit, with_vector=with_vectors + ) + + return [ + await query_search(query_vector) for query_vector in await self.embed_data(query_texts) + ] - return [await query_search(query_vector) for query_vector in await self.embed_data(query_texts)] - async def delete_data_points(self, collection_name: str, data_point_ids: list[str]): from weaviate.classes.query import Filter + future = asyncio.Future() result = self.get_collection(collection_name).data.delete_many( - filters = Filter.by_id().contains_any(data_point_ids) + filters=Filter.by_id().contains_any(data_point_ids) ) future.set_result(result) diff --git a/cognee/infrastructure/engine/models/DataPoint.py b/cognee/infrastructure/engine/models/DataPoint.py index e08041146..db0d9308a 100644 --- a/cognee/infrastructure/engine/models/DataPoint.py +++ b/cognee/infrastructure/engine/models/DataPoint.py @@ -9,23 +9,24 @@ class MetaData(TypedDict): index_fields: list[str] + class DataPoint(BaseModel): __tablename__ = "data_point" - id: UUID = Field(default_factory = uuid4) + id: UUID = Field(default_factory=uuid4) updated_at: Optional[datetime] = datetime.now(timezone.utc) topological_rank: Optional[int] = 0 - _metadata: Optional[MetaData] = { - "index_fields": [], - "type": "DataPoint" - } + _metadata: Optional[MetaData] = {"index_fields": [], "type": "DataPoint"} # class Config: # underscore_attrs_are_private = True @classmethod def get_embeddable_data(self, data_point): - if data_point._metadata and len(data_point._metadata["index_fields"]) > 0 \ - and hasattr(data_point, data_point._metadata["index_fields"][0]): + if ( + data_point._metadata + and len(data_point._metadata["index_fields"]) > 0 + and hasattr(data_point, data_point._metadata["index_fields"][0]) + ): attribute = getattr(data_point, data_point._metadata["index_fields"][0]) if isinstance(attribute, str): @@ -36,10 +37,12 @@ def get_embeddable_data(self, data_point): @classmethod def get_embeddable_properties(self, data_point): if data_point._metadata and len(data_point._metadata["index_fields"]) > 0: - return [getattr(data_point, field, None) for field in data_point._metadata["index_fields"]] + return [ + getattr(data_point, field, None) for field in data_point._metadata["index_fields"] + ] return [] @classmethod def get_embeddable_property_names(self, data_point): - return data_point._metadata["index_fields"] or [] \ No newline at end of file + return data_point._metadata["index_fields"] or [] diff --git a/cognee/infrastructure/files/add_file_to_storage.py b/cognee/infrastructure/files/add_file_to_storage.py index e2a324a83..78713f447 100644 --- a/cognee/infrastructure/files/add_file_to_storage.py +++ b/cognee/infrastructure/files/add_file_to_storage.py @@ -3,6 +3,7 @@ from .storage.StorageManager import StorageManager from .storage.LocalStorage import LocalStorage + async def add_file_to_storage(file_path: str, file: BinaryIO): storage_manager = StorageManager(LocalStorage(get_absolute_path("data/files"))) diff --git a/cognee/infrastructure/files/remove_file_from_storage.py b/cognee/infrastructure/files/remove_file_from_storage.py index b0b9a5a61..b657ead43 100644 --- a/cognee/infrastructure/files/remove_file_from_storage.py +++ b/cognee/infrastructure/files/remove_file_from_storage.py @@ -2,6 +2,7 @@ from .storage.StorageManager import StorageManager from .storage.LocalStorage import LocalStorage + async def remove_file_from_storage(file_path: str): storage_manager = StorageManager(LocalStorage(get_absolute_path("data/files"))) diff --git a/cognee/infrastructure/files/storage/LocalStorage.py b/cognee/infrastructure/files/storage/LocalStorage.py index 4db17c91b..7ef9131d9 100644 --- a/cognee/infrastructure/files/storage/LocalStorage.py +++ b/cognee/infrastructure/files/storage/LocalStorage.py @@ -3,6 +3,7 @@ from typing import BinaryIO, Union from .StorageManager import Storage + class LocalStorage(Storage): storage_path: str = None @@ -16,8 +17,8 @@ def store(self, file_path: str, data: Union[BinaryIO, str]): with open( full_file_path, - mode = "w" if isinstance(data, str) else "wb", - encoding = "utf-8" if isinstance(data, str) else None + mode="w" if isinstance(data, str) else "wb", + encoding="utf-8" if isinstance(data, str) else None, ) as f: if hasattr(data, "read"): data.seek(0) @@ -28,7 +29,7 @@ def store(self, file_path: str, data: Union[BinaryIO, str]): def retrieve(self, file_path: str, mode: str = "rb"): full_file_path = self.storage_path + "/" + file_path - with open(full_file_path, mode = mode) as f: + with open(full_file_path, mode=mode) as f: f.seek(0) return f.read() @@ -39,7 +40,7 @@ def file_exists(file_path: str): @staticmethod def ensure_directory_exists(file_path: str): if not os.path.exists(file_path): - os.makedirs(file_path, exist_ok = True) + os.makedirs(file_path, exist_ok=True) @staticmethod def remove(file_path: str): diff --git a/cognee/infrastructure/files/storage/StorageManager.py b/cognee/infrastructure/files/storage/StorageManager.py index 1db2d5d31..389800e00 100644 --- a/cognee/infrastructure/files/storage/StorageManager.py +++ b/cognee/infrastructure/files/storage/StorageManager.py @@ -1,5 +1,6 @@ from typing import Protocol, BinaryIO + class Storage(Protocol): def store(self, file_path: str, data: bytes): pass @@ -11,10 +12,11 @@ def retrieve(self, file_path: str): def remove(file_path: str): pass -class StorageManager(): + +class StorageManager: storage: Storage = None - def __init__ (self, storage: Storage): + def __init__(self, storage: Storage): self.storage = storage def store(self, file_path: str, data: BinaryIO): diff --git a/cognee/infrastructure/files/utils/extract_text_from_file.py b/cognee/infrastructure/files/utils/extract_text_from_file.py index 171128b2e..7a5e16829 100644 --- a/cognee/infrastructure/files/utils/extract_text_from_file.py +++ b/cognee/infrastructure/files/utils/extract_text_from_file.py @@ -2,10 +2,11 @@ from pypdf import PdfReader import filetype + def extract_text_from_file(file: BinaryIO, file_type: filetype.Type) -> str: """Extract text from a file""" if file_type.extension == "pdf": - reader = PdfReader(stream = file) + reader = PdfReader(stream=file) pages = list(reader.pages[:3]) return "\n".join([page.extract_text().strip() for page in pages]) diff --git a/cognee/infrastructure/files/utils/get_file_metadata.py b/cognee/infrastructure/files/utils/get_file_metadata.py index 89c3d6d8e..4bce29f60 100644 --- a/cognee/infrastructure/files/utils/get_file_metadata.py +++ b/cognee/infrastructure/files/utils/get_file_metadata.py @@ -11,6 +11,7 @@ class FileMetadata(TypedDict): extension: str content_hash: str + def get_file_metadata(file: BinaryIO) -> FileMetadata: """Get metadata from a file""" file.seek(0) @@ -23,9 +24,9 @@ def get_file_metadata(file: BinaryIO) -> FileMetadata: file_name = file_path.split("/")[-1].split(".")[0] if file_path else None return FileMetadata( - name = file_name, - file_path = file_path, - mime_type = file_type.mime, - extension = file_type.extension, - content_hash = content_hash, + name=file_name, + file_path=file_path, + mime_type=file_type.mime, + extension=file_type.extension, + content_hash=content_hash, ) diff --git a/cognee/infrastructure/files/utils/guess_file_type.py b/cognee/infrastructure/files/utils/guess_file_type.py index 001585945..3e26827d8 100644 --- a/cognee/infrastructure/files/utils/guess_file_type.py +++ b/cognee/infrastructure/files/utils/guess_file_type.py @@ -2,37 +2,45 @@ import filetype from .is_text_content import is_text_content + class FileTypeException(Exception): message: str def __init__(self, message: str): self.message = message + class TxtFileType(filetype.Type): """Text file type""" + MIME = "text/plain" EXTENSION = "txt" def __init__(self): - super(TxtFileType, self).__init__(mime = TxtFileType.MIME, extension = TxtFileType.EXTENSION) + super(TxtFileType, self).__init__(mime=TxtFileType.MIME, extension=TxtFileType.EXTENSION) def match(self, buf): return is_text_content(buf) + txt_file_type = TxtFileType() filetype.add_type(txt_file_type) + class CustomPdfMatcher(filetype.Type): MIME = "application/pdf" EXTENSION = "pdf" def __init__(self): - super(CustomPdfMatcher, self).__init__(mime = CustomPdfMatcher.MIME, extension = CustomPdfMatcher.EXTENSION) + super(CustomPdfMatcher, self).__init__( + mime=CustomPdfMatcher.MIME, extension=CustomPdfMatcher.EXTENSION + ) def match(self, buf): return b"PDF-" in buf + custom_pdf_matcher = CustomPdfMatcher() filetype.add_type(custom_pdf_matcher) diff --git a/cognee/infrastructure/files/utils/is_text_content.py b/cognee/infrastructure/files/utils/is_text_content.py index dc323cd2a..83a961e89 100644 --- a/cognee/infrastructure/files/utils/is_text_content.py +++ b/cognee/infrastructure/files/utils/is_text_content.py @@ -1,24 +1,27 @@ def is_text_content(content): """Check if the content is text.""" # Check for null bytes - if b'\0' in content: + if b"\0" in content: return False # Check for common text encodings (BOMs) - if content.startswith((b'\xEF\xBB\xBF', # UTF-8 - b'\xFF\xFE', # UTF-16 LE - b'\xFE\xFF', # UTF-16 BE - b'\x00\x00\xFE\xFF', # UTF-32 LE - b'\xFF\xFE\x00\x00', # UTF-32 BE - )): + if content.startswith( + ( + b"\xef\xbb\xbf", # UTF-8 + b"\xff\xfe", # UTF-16 LE + b"\xfe\xff", # UTF-16 BE + b"\x00\x00\xfe\xff", # UTF-32 LE + b"\xff\xfe\x00\x00", # UTF-32 BE + ) + ): return True # Check for ASCII characters - if all(0x20 <= byte <= 0x7E or byte in (b'\n', b'\r', b'\t') for byte in content): + if all(0x20 <= byte <= 0x7E or byte in (b"\n", b"\r", b"\t") for byte in content): return True # Check for common line break characters - if b'\n' in content or b'\r' in content: + if b"\n" in content or b"\r" in content: return True # If no obvious indicators found, assume it's a text file diff --git a/cognee/infrastructure/llm/anthropic/adapter.py b/cognee/infrastructure/llm/anthropic/adapter.py index 119b05eff..1fba732a0 100644 --- a/cognee/infrastructure/llm/anthropic/adapter.py +++ b/cognee/infrastructure/llm/anthropic/adapter.py @@ -10,34 +10,33 @@ class AnthropicAdapter(LLMInterface): """Adapter for Anthropic API""" + name = "Anthropic" model: str def __init__(self, model: str = None): self.aclient = instructor.patch( - create = anthropic.Anthropic().messages.create, - mode = instructor.Mode.ANTHROPIC_TOOLS + create=anthropic.Anthropic().messages.create, mode=instructor.Mode.ANTHROPIC_TOOLS ) self.model = model async def acreate_structured_output( - self, - text_input: str, - system_prompt: str, - response_model: Type[BaseModel] + self, text_input: str, system_prompt: str, response_model: Type[BaseModel] ) -> BaseModel: """Generate a response from a user query.""" return await self.aclient( - model = self.model, - max_tokens = 4096, - max_retries = 5, - messages = [{ - "role": "user", - "content": f"""Use the given format to extract information + model=self.model, + max_tokens=4096, + max_retries=5, + messages=[ + { + "role": "user", + "content": f"""Use the given format to extract information from the following input: {text_input}. {system_prompt}""", - }], - response_model = response_model, + } + ], + response_model=response_model, ) def show_prompt(self, text_input: str, system_prompt: str) -> str: @@ -50,6 +49,10 @@ def show_prompt(self, text_input: str, system_prompt: str) -> str: system_prompt = read_query_prompt(system_prompt) - formatted_prompt = f"""System Prompt:\n{system_prompt}\n\nUser Input:\n{text_input}\n""" if system_prompt else None + formatted_prompt = ( + f"""System Prompt:\n{system_prompt}\n\nUser Input:\n{text_input}\n""" + if system_prompt + else None + ) return formatted_prompt diff --git a/cognee/infrastructure/llm/config.py b/cognee/infrastructure/llm/config.py index d148042be..67fc82683 100644 --- a/cognee/infrastructure/llm/config.py +++ b/cognee/infrastructure/llm/config.py @@ -2,6 +2,7 @@ from functools import lru_cache from pydantic_settings import BaseSettings, SettingsConfigDict + class LLMConfig(BaseSettings): llm_provider: str = "openai" llm_model: str = "gpt-4o-mini" @@ -12,7 +13,7 @@ class LLMConfig(BaseSettings): llm_streaming: bool = False transcription_model: str = "whisper-1" - model_config = SettingsConfigDict(env_file = ".env", extra = "allow") + model_config = SettingsConfigDict(env_file=".env", extra="allow") def to_dict(self) -> dict: return { @@ -23,9 +24,10 @@ def to_dict(self) -> dict: "api_version": self.llm_api_version, "temperature": self.llm_temperature, "streaming": self.llm_streaming, - "transcription_model": self.transcription_model + "transcription_model": self.transcription_model, } + @lru_cache def get_llm_config(): return LLMConfig() diff --git a/cognee/infrastructure/llm/generic_llm_api/adapter.py b/cognee/infrastructure/llm/generic_llm_api/adapter.py index 294af9256..a910c0780 100644 --- a/cognee/infrastructure/llm/generic_llm_api/adapter.py +++ b/cognee/infrastructure/llm/generic_llm_api/adapter.py @@ -1,82 +1,54 @@ -'''Adapter for Generic API LLM provider API''' +"""Adapter for Generic API LLM provider API""" + import asyncio from typing import List, Type from pydantic import BaseModel import instructor -import openai - -from cognee.exceptions import InvalidValueError from cognee.infrastructure.llm.llm_interface import LLMInterface -from cognee.infrastructure.llm.prompts import read_query_prompt -from cognee.shared.data_models import MonitoringTool -from cognee.base_config import get_base_config from cognee.infrastructure.llm.config import get_llm_config +import litellm class GenericAPIAdapter(LLMInterface): - """Adapter for Generic API LLM provider API """ + """Adapter for Generic API LLM provider API""" + name: str model: str api_key: str - def __init__(self, api_endpoint, api_key: str, model: str, name: str): + def __init__(self, endpoint, api_key: str, model: str, name: str): self.name = name self.model = model self.api_key = api_key + self.endpoint = endpoint llm_config = get_llm_config() - if llm_config.llm_provider == "groq": - from groq import groq - self.aclient = instructor.from_openai( - client = groq.Groq( - api_key = api_key, - ), - mode = instructor.Mode.MD_JSON - ) - else: - base_config = get_base_config() - - if base_config.monitoring_tool == MonitoringTool.LANGFUSE: - from langfuse.openai import AsyncOpenAI - elif base_config.monitoring_tool == MonitoringTool.LANGSMITH: - from langsmith import wrappers - from openai import AsyncOpenAI - AsyncOpenAI = wrappers.wrap_openai(AsyncOpenAI()) - else: - from openai import AsyncOpenAI + if llm_config.llm_provider == "ollama": + self.aclient = instructor.from_litellm(litellm.acompletion) - self.aclient = instructor.patch( - AsyncOpenAI( - base_url = api_endpoint, - api_key = api_key, # required, but unused - ), - mode = instructor.Mode.JSON, - ) + else: + self.aclient = instructor.from_litellm(litellm.acompletion) - async def acreate_structured_output(self, text_input: str, system_prompt: str, response_model: Type[BaseModel]) -> BaseModel: + async def acreate_structured_output( + self, text_input: str, system_prompt: str, response_model: Type[BaseModel] + ) -> BaseModel: """Generate a response from a user query.""" return await self.aclient.chat.completions.create( - model = self.model, - messages = [ + model=self.model, + messages=[ { "role": "user", "content": f"""Use the given format to - extract information from the following input: {text_input}. """, + extract information from the following input: {text_input}. """, + }, + { + "role": "system", + "content": system_prompt, }, - {"role": "system", "content": system_prompt}, ], - response_model = response_model, + max_retries=5, + api_base=self.endpoint, + response_model=response_model, ) - - def show_prompt(self, text_input: str, system_prompt: str) -> str: - """Format and display the prompt for a user query.""" - if not text_input: - text_input = "No user input provided." - if not system_prompt: - raise InvalidValueError(message="No system prompt path provided.") - system_prompt = read_query_prompt(system_prompt) - - formatted_prompt = f"""System Prompt:\n{system_prompt}\n\nUser Input:\n{text_input}\n""" if system_prompt else None - return formatted_prompt diff --git a/cognee/infrastructure/llm/get_llm_client.py b/cognee/infrastructure/llm/get_llm_client.py index 9a23892f2..0f64014e3 100644 --- a/cognee/infrastructure/llm/get_llm_client.py +++ b/cognee/infrastructure/llm/get_llm_client.py @@ -1,9 +1,11 @@ """Get the LLM client.""" + from enum import Enum from cognee.exceptions import InvalidValueError from cognee.infrastructure.llm import get_llm_config + # Define an Enum for LLM Providers class LLMProvider(Enum): OPENAI = "openai" @@ -11,6 +13,7 @@ class LLMProvider(Enum): ANTHROPIC = "anthropic" CUSTOM = "custom" + def get_llm_client(): """Get the LLM client based on the configuration using Enums.""" llm_config = get_llm_config() @@ -24,12 +27,12 @@ def get_llm_client(): from .openai.adapter import OpenAIAdapter return OpenAIAdapter( - api_key = llm_config.llm_api_key, - endpoint = llm_config.llm_endpoint, - api_version = llm_config.llm_api_version, - model = llm_config.llm_model, - transcription_model = llm_config.transcription_model, - streaming = llm_config.llm_streaming, + api_key=llm_config.llm_api_key, + endpoint=llm_config.llm_endpoint, + api_version=llm_config.llm_api_version, + model=llm_config.llm_model, + transcription_model=llm_config.transcription_model, + streaming=llm_config.llm_streaming, ) elif provider == LLMProvider.OLLAMA: @@ -37,10 +40,14 @@ def get_llm_client(): raise InvalidValueError(message="LLM API key is not set.") from .generic_llm_api.adapter import GenericAPIAdapter - return GenericAPIAdapter(llm_config.llm_endpoint, llm_config.llm_api_key, llm_config.llm_model, "Ollama") + + return GenericAPIAdapter( + llm_config.llm_endpoint, llm_config.llm_api_key, llm_config.llm_model, "Ollama" + ) elif provider == LLMProvider.ANTHROPIC: from .anthropic.adapter import AnthropicAdapter + return AnthropicAdapter(llm_config.llm_model) elif provider == LLMProvider.CUSTOM: @@ -48,7 +55,10 @@ def get_llm_client(): raise InvalidValueError(message="LLM API key is not set.") from .generic_llm_api.adapter import GenericAPIAdapter - return GenericAPIAdapter(llm_config.llm_endpoint, llm_config.llm_api_key, llm_config.llm_model, "Custom") + + return GenericAPIAdapter( + llm_config.llm_endpoint, llm_config.llm_api_key, llm_config.llm_model, "Custom" + ) else: raise InvalidValueError(message=f"Unsupported LLM provider: {provider}") diff --git a/cognee/infrastructure/llm/llm_interface.py b/cognee/infrastructure/llm/llm_interface.py index 4a5bd79b6..dfcbecd29 100644 --- a/cognee/infrastructure/llm/llm_interface.py +++ b/cognee/infrastructure/llm/llm_interface.py @@ -1,18 +1,18 @@ -""" LLM Interface """ +"""LLM Interface""" from typing import Type, Protocol from abc import abstractmethod from pydantic import BaseModel from cognee.infrastructure.llm.prompts import read_query_prompt + class LLMInterface(Protocol): - """ LLM Interface """ + """LLM Interface""" @abstractmethod - async def acreate_structured_output(self, - text_input: str, - system_prompt: str, - response_model: Type[BaseModel]) -> BaseModel: + async def acreate_structured_output( + self, text_input: str, system_prompt: str, response_model: Type[BaseModel] + ) -> BaseModel: """To get structured output, import/call this function""" raise NotImplementedError diff --git a/cognee/infrastructure/llm/openai/adapter.py b/cognee/infrastructure/llm/openai/adapter.py index bb5af15f2..d45662380 100644 --- a/cognee/infrastructure/llm/openai/adapter.py +++ b/cognee/infrastructure/llm/openai/adapter.py @@ -16,6 +16,7 @@ if monitoring == MonitoringTool.LANGFUSE: from langfuse.decorators import observe + class OpenAIAdapter(LLMInterface): name = "OpenAI" model: str @@ -25,13 +26,13 @@ class OpenAIAdapter(LLMInterface): """Adapter for OpenAI's GPT-3, GPT=4 API""" def __init__( - self, - api_key: str, - endpoint: str, - api_version: str, - model: str, - transcription_model: str, - streaming: bool = False, + self, + api_key: str, + endpoint: str, + api_version: str, + model: str, + transcription_model: str, + streaming: bool = False, ): self.aclient = instructor.from_litellm(litellm.acompletion) self.client = instructor.from_litellm(litellm.completion) @@ -41,25 +42,26 @@ def __init__( self.endpoint = endpoint self.api_version = api_version self.streaming = streaming - base_config = get_base_config() - - - @observe(as_type='generation') - async def acreate_structured_output(self, text_input: str, system_prompt: str, - response_model: Type[BaseModel]) -> BaseModel: + @observe(as_type="generation") + async def acreate_structured_output( + self, text_input: str, system_prompt: str, response_model: Type[BaseModel] + ) -> BaseModel: """Generate a response from a user query.""" return await self.aclient.chat.completions.create( model=self.model, - messages=[{ - "role": "user", - "content": f"""Use the given format to + messages=[ + { + "role": "user", + "content": f"""Use the given format to extract information from the following input: {text_input}. """, - }, { - "role": "system", - "content": system_prompt, - }], + }, + { + "role": "system", + "content": system_prompt, + }, + ], api_key=self.api_key, api_base=self.endpoint, api_version=self.api_version, @@ -68,20 +70,24 @@ async def acreate_structured_output(self, text_input: str, system_prompt: str, ) @observe - def create_structured_output(self, text_input: str, system_prompt: str, - response_model: Type[BaseModel]) -> BaseModel: + def create_structured_output( + self, text_input: str, system_prompt: str, response_model: Type[BaseModel] + ) -> BaseModel: """Generate a response from a user query.""" return self.client.chat.completions.create( model=self.model, - messages=[{ - "role": "user", - "content": f"""Use the given format to + messages=[ + { + "role": "user", + "content": f"""Use the given format to extract information from the following input: {text_input}. """, - }, { - "role": "system", - "content": system_prompt, - }], + }, + { + "role": "system", + "content": system_prompt, + }, + ], api_key=self.api_key, api_base=self.endpoint, api_version=self.api_version, @@ -111,24 +117,27 @@ def create_transcript(self, input): def transcribe_image(self, input) -> BaseModel: with open(input, "rb") as image_file: - encoded_image = base64.b64encode(image_file.read()).decode('utf-8') + encoded_image = base64.b64encode(image_file.read()).decode("utf-8") return litellm.completion( model=self.model, - messages=[{ - "role": "user", - "content": [ - { - "type": "text", - "text": "What’s in this image?", - }, { - "type": "image_url", - "image_url": { - "url": f"data:image/jpeg;base64,{encoded_image}", + messages=[ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "What’s in this image?", }, - }, - ], - }], + { + "type": "image_url", + "image_url": { + "url": f"data:image/jpeg;base64,{encoded_image}", + }, + }, + ], + } + ], api_key=self.api_key, api_base=self.endpoint, api_version=self.api_version, @@ -144,5 +153,9 @@ def show_prompt(self, text_input: str, system_prompt: str) -> str: raise InvalidValueError(message="No system prompt path provided.") system_prompt = read_query_prompt(system_prompt) - formatted_prompt = f"""System Prompt:\n{system_prompt}\n\nUser Input:\n{text_input}\n""" if system_prompt else None - return formatted_prompt \ No newline at end of file + formatted_prompt = ( + f"""System Prompt:\n{system_prompt}\n\nUser Input:\n{text_input}\n""" + if system_prompt + else None + ) + return formatted_prompt diff --git a/cognee/infrastructure/llm/prompts/read_query_prompt.py b/cognee/infrastructure/llm/prompts/read_query_prompt.py index d9ea55acd..c1f58d77f 100644 --- a/cognee/infrastructure/llm/prompts/read_query_prompt.py +++ b/cognee/infrastructure/llm/prompts/read_query_prompt.py @@ -2,12 +2,13 @@ import logging from cognee.root_dir import get_absolute_path + def read_query_prompt(prompt_file_name: str): """Read a query prompt from a file.""" try: file_path = path.join(get_absolute_path("./infrastructure/llm/prompts"), prompt_file_name) - with open(file_path, "r", encoding = "utf-8") as file: + with open(file_path, "r", encoding="utf-8") as file: return file.read() except FileNotFoundError: logging.error(f"Error: Prompt file not found. Attempted to read: %s {file_path}") diff --git a/cognee/infrastructure/llm/prompts/render_prompt.py b/cognee/infrastructure/llm/prompts/render_prompt.py index 03f1f7cb2..756fa671b 100644 --- a/cognee/infrastructure/llm/prompts/render_prompt.py +++ b/cognee/infrastructure/llm/prompts/render_prompt.py @@ -1,7 +1,8 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape from cognee.root_dir import get_absolute_path -def render_prompt(filename: str, context: dict) -> str: + +def render_prompt(filename: str, context: dict) -> str: """Render a Jinja2 template asynchronously. :param filename: The name of the template file to render. :param context: The context to render the template with. @@ -12,8 +13,8 @@ def render_prompt(filename: str, context: dict) -> str: # Initialize the Jinja2 environment to load templates from the filesystem env = Environment( - loader = FileSystemLoader(base_directory), - autoescape = select_autoescape(["html", "xml", "txt"]) + loader=FileSystemLoader(base_directory), + autoescape=select_autoescape(["html", "xml", "txt"]), ) # Load the template by name diff --git a/cognee/infrastructure/pipeline/models/Operation.py b/cognee/infrastructure/pipeline/models/Operation.py index 62eb74c44..b0e5624cb 100644 --- a/cognee/infrastructure/pipeline/models/Operation.py +++ b/cognee/infrastructure/pipeline/models/Operation.py @@ -3,10 +3,12 @@ from sqlalchemy import Column, DateTime, ForeignKey, Enum, JSON from cognee.infrastructure.databases.relational import Base, UUID + class OperationType(Enum): MERGE_DATA = "MERGE_DATA" APPEND_DATA = "APPEND_DATA" + class OperationStatus(Enum): STARTED = "OPERATION_STARTED" IN_PROGRESS = "OPERATION_IN_PROGRESS" @@ -14,14 +16,15 @@ class OperationStatus(Enum): ERROR = "OPERATION_ERROR" CANCELLED = "OPERATION_CANCELLED" + class Operation(Base): __tablename__ = "operation" - id = Column(UUID, primary_key = True) + id = Column(UUID, primary_key=True) status = Column(Enum(OperationStatus)) operation_type = Column(Enum(OperationType)) data_id = Column(UUID, ForeignKey("data.id")) - meta_data: Mapped[dict] = MappedColumn(type_ = JSON) + meta_data: Mapped[dict] = MappedColumn(type_=JSON) - created_at = Column(DateTime, default = datetime.now(timezone.utc)) + created_at = Column(DateTime, default=datetime.now(timezone.utc)) diff --git a/cognee/modules/chunking/TextChunker.py b/cognee/modules/chunking/TextChunker.py index 64c7aae5c..7bb8a1c1c 100644 --- a/cognee/modules/chunking/TextChunker.py +++ b/cognee/modules/chunking/TextChunker.py @@ -3,7 +3,8 @@ from .models.DocumentChunk import DocumentChunk from cognee.tasks.chunks import chunk_by_paragraph -class TextChunker(): + +class TextChunker: document = None max_chunk_size: int @@ -21,7 +22,7 @@ def read(self): for chunk_data in chunk_by_paragraph( content_text, self.max_chunk_size, - batch_paragraphs = True, + batch_paragraphs=True, ): if self.chunk_size + chunk_data["word_count"] <= self.max_chunk_size: paragraph_chunks.append(chunk_data) @@ -29,17 +30,17 @@ def read(self): else: if len(paragraph_chunks) == 0: yield DocumentChunk( - id = chunk_data["chunk_id"], - text = chunk_data["text"], - word_count = chunk_data["word_count"], - is_part_of = self.document, - chunk_index = self.chunk_index, - cut_type = chunk_data["cut_type"], - contains = [], - _metadata = { + id=chunk_data["chunk_id"], + text=chunk_data["text"], + word_count=chunk_data["word_count"], + is_part_of=self.document, + chunk_index=self.chunk_index, + cut_type=chunk_data["cut_type"], + contains=[], + _metadata={ "index_fields": ["text"], - "metadata_id": self.document.metadata_id - } + "metadata_id": self.document.metadata_id, + }, ) paragraph_chunks = [] self.chunk_size = 0 @@ -47,17 +48,19 @@ def read(self): chunk_text = " ".join(chunk["text"] for chunk in paragraph_chunks) try: yield DocumentChunk( - id = uuid5(NAMESPACE_OID, f"{str(self.document.id)}-{self.chunk_index}"), - text = chunk_text, - word_count = self.chunk_size, - is_part_of = self.document, - chunk_index = self.chunk_index, - cut_type = paragraph_chunks[len(paragraph_chunks) - 1]["cut_type"], - contains = [], - _metadata = { + id=uuid5( + NAMESPACE_OID, f"{str(self.document.id)}-{self.chunk_index}" + ), + text=chunk_text, + word_count=self.chunk_size, + is_part_of=self.document, + chunk_index=self.chunk_index, + cut_type=paragraph_chunks[len(paragraph_chunks) - 1]["cut_type"], + contains=[], + _metadata={ "index_fields": ["text"], - "metadata_id": self.document.metadata_id - } + "metadata_id": self.document.metadata_id, + }, ) except Exception as e: print(e) @@ -69,17 +72,14 @@ def read(self): if len(paragraph_chunks) > 0: try: yield DocumentChunk( - id = uuid5(NAMESPACE_OID, f"{str(self.document.id)}-{self.chunk_index}"), - text = " ".join(chunk["text"] for chunk in paragraph_chunks), - word_count = self.chunk_size, - is_part_of = self.document, - chunk_index = self.chunk_index, - cut_type = paragraph_chunks[len(paragraph_chunks) - 1]["cut_type"], - contains = [], - _metadata = { - "index_fields": ["text"], - "metadata_id": self.document.metadata_id - } + id=uuid5(NAMESPACE_OID, f"{str(self.document.id)}-{self.chunk_index}"), + text=" ".join(chunk["text"] for chunk in paragraph_chunks), + word_count=self.chunk_size, + is_part_of=self.document, + chunk_index=self.chunk_index, + cut_type=paragraph_chunks[len(paragraph_chunks) - 1]["cut_type"], + contains=[], + _metadata={"index_fields": ["text"], "metadata_id": self.document.metadata_id}, ) except Exception as e: print(e) diff --git a/cognee/modules/chunking/models/DocumentChunk.py b/cognee/modules/chunking/models/DocumentChunk.py index 8729596df..4920e9b06 100644 --- a/cognee/modules/chunking/models/DocumentChunk.py +++ b/cognee/modules/chunking/models/DocumentChunk.py @@ -14,7 +14,4 @@ class DocumentChunk(DataPoint): is_part_of: Document contains: List[Entity] = None - _metadata: dict = { - "index_fields": ["text"], - "type": "DocumentChunk" - } + _metadata: dict = {"index_fields": ["text"], "type": "DocumentChunk"} diff --git a/cognee/modules/cognify/config.py b/cognee/modules/cognify/config.py index ffc54856c..d40410bfc 100644 --- a/cognee/modules/cognify/config.py +++ b/cognee/modules/cognify/config.py @@ -2,11 +2,12 @@ from pydantic_settings import BaseSettings, SettingsConfigDict from cognee.shared.data_models import DefaultContentPrediction, SummarizedContent + class CognifyConfig(BaseSettings): classification_model: object = DefaultContentPrediction summarization_model: object = SummarizedContent - model_config = SettingsConfigDict(env_file = ".env", extra = "allow") + model_config = SettingsConfigDict(env_file=".env", extra="allow") def to_dict(self) -> dict: return { @@ -14,6 +15,7 @@ def to_dict(self) -> dict: "summarization_model": self.summarization_model, } + @lru_cache def get_cognify_config(): return CognifyConfig() diff --git a/cognee/modules/data/deletion/prune_data.py b/cognee/modules/data/deletion/prune_data.py index cef0c5ffa..12a516329 100644 --- a/cognee/modules/data/deletion/prune_data.py +++ b/cognee/modules/data/deletion/prune_data.py @@ -1,6 +1,7 @@ from cognee.base_config import get_base_config from cognee.infrastructure.files.storage import LocalStorage + async def prune_data(): base_config = get_base_config() data_root_directory = base_config.data_root_directory diff --git a/cognee/modules/data/deletion/prune_system.py b/cognee/modules/data/deletion/prune_system.py index 38c625558..055d69b55 100644 --- a/cognee/modules/data/deletion/prune_system.py +++ b/cognee/modules/data/deletion/prune_system.py @@ -2,7 +2,8 @@ from cognee.infrastructure.databases.graph.get_graph_engine import get_graph_engine from cognee.infrastructure.databases.relational import get_relational_engine -async def prune_system(graph = True, vector = True, metadata = False): + +async def prune_system(graph=True, vector=True, metadata=False): if graph: graph_engine = await get_graph_engine() await graph_engine.delete_graph() diff --git a/cognee/modules/data/exceptions/__init__.py b/cognee/modules/data/exceptions/__init__.py index 6f74c627e..16c363bed 100644 --- a/cognee/modules/data/exceptions/__init__.py +++ b/cognee/modules/data/exceptions/__init__.py @@ -7,4 +7,4 @@ from .exceptions import ( UnstructuredLibraryImportError, UnauthorizedDataAccessError, -) \ No newline at end of file +) diff --git a/cognee/modules/data/exceptions/exceptions.py b/cognee/modules/data/exceptions/exceptions.py index 5117f3cac..cebc1efe5 100644 --- a/cognee/modules/data/exceptions/exceptions.py +++ b/cognee/modules/data/exceptions/exceptions.py @@ -1,20 +1,22 @@ from cognee.exceptions import CogneeApiError from fastapi import status + class UnstructuredLibraryImportError(CogneeApiError): def __init__( - self, - message: str = "Import error. Unstructured library is not installed.", - name: str = "UnstructuredModuleImportError", - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + self, + message: str = "Import error. Unstructured library is not installed.", + name: str = "UnstructuredModuleImportError", + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, ): super().__init__(message, name, status_code) + class UnauthorizedDataAccessError(CogneeApiError): def __init__( - self, - message: str = "User does not have permission to access this data.", - name: str = "UnauthorizedDataAccessError", - status_code=status.HTTP_401_UNAUTHORIZED, + self, + message: str = "User does not have permission to access this data.", + name: str = "UnauthorizedDataAccessError", + status_code=status.HTTP_401_UNAUTHORIZED, ): - super().__init__(message, name, status_code) \ No newline at end of file + super().__init__(message, name, status_code) diff --git a/cognee/modules/data/extraction/extract_summary.py b/cognee/modules/data/extraction/extract_summary.py index 0d3feef95..27b3997f3 100644 --- a/cognee/modules/data/extraction/extract_summary.py +++ b/cognee/modules/data/extraction/extract_summary.py @@ -13,15 +13,17 @@ logger = logging.getLogger("extract_summary") + async def extract_summary(content: str, response_model: Type[BaseModel]): llm_client = get_llm_client() system_prompt = read_query_prompt("summarize_content.txt") llm_output = await llm_client.acreate_structured_output(content, system_prompt, response_model) - + return llm_output + async def extract_code_summary(content: str): enable_mocking = os.getenv("MOCK_CODE_SUMMARY", "false") if isinstance(enable_mocking, bool): diff --git a/cognee/modules/data/extraction/extract_topics_naive.py b/cognee/modules/data/extraction/extract_topics_naive.py index a8487224f..d1323e24b 100644 --- a/cognee/modules/data/extraction/extract_topics_naive.py +++ b/cognee/modules/data/extraction/extract_topics_naive.py @@ -6,6 +6,7 @@ from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import TruncatedSVD + def extract_topics(text: str): sentences = sent_tokenize(text) @@ -18,24 +19,19 @@ def extract_topics(text: str): lemmatizer = WordNetLemmatizer() base_notation_sentences = [lemmatizer.lemmatize(sentence) for sentence in sentences] - tf_vectorizer = TfidfVectorizer(tokenizer = word_tokenize, token_pattern = None) + tf_vectorizer = TfidfVectorizer(tokenizer=word_tokenize, token_pattern=None) transformed_corpus = tf_vectorizer.fit_transform(base_notation_sentences) - svd = TruncatedSVD(n_components = 10) + svd = TruncatedSVD(n_components=10) svd_corpus = svd.fit(transformed_corpus) - feature_scores = dict( - zip( - tf_vectorizer.vocabulary_, - svd_corpus.components_[0] - ) - ) + feature_scores = dict(zip(tf_vectorizer.vocabulary_, svd_corpus.components_[0])) topics = sorted( feature_scores, # key = feature_scores.get, - key = lambda x: transformed_corpus[0, tf_vectorizer.vocabulary_[x]], - reverse = True, + key=lambda x: transformed_corpus[0, tf_vectorizer.vocabulary_[x]], + reverse=True, )[:10] return topics @@ -45,6 +41,7 @@ def clean_text(text: str): text = re.sub(r"[ \t]{2,}|[\n\r]", " ", text.lower()) return re.sub(r"[`\"'.,;!?…]", "", text).strip() + def remove_stop_words(text: str): try: stopwords.ensure_loaded() @@ -54,7 +51,7 @@ def remove_stop_words(text: str): stop_words = set(stopwords.words("english")) text = text.split() - text = [word for word in text if not word in stop_words] + text = [word for word in text if word not in stop_words] return " ".join(text) diff --git a/cognee/modules/data/extraction/knowledge_graph/add_model_class_to_graph.py b/cognee/modules/data/extraction/knowledge_graph/add_model_class_to_graph.py index 08691cbf2..9da226541 100644 --- a/cognee/modules/data/extraction/knowledge_graph/add_model_class_to_graph.py +++ b/cognee/modules/data/extraction/knowledge_graph/add_model_class_to_graph.py @@ -2,6 +2,7 @@ from pydantic import BaseModel from cognee.infrastructure.databases.graph.graph_db_interface import GraphDBInterface + async def add_model_class_to_graph( model_class: Type[BaseModel], graph: GraphDBInterface, @@ -13,7 +14,7 @@ async def add_model_class_to_graph( if await graph.extract_node(model_name): return - await graph.add_node(model_name, dict(type = "model")) + await graph.add_node(model_name, dict(type="model")) if parent and relationship: await graph.add_edge( @@ -21,9 +22,9 @@ async def add_model_class_to_graph( model_name, relationship, dict( - relationship_name = relationship, - source_node_id = parent, - target_node_id = model_name, + relationship_name=relationship, + source_node_id=parent, + target_node_id=model_name, ), ) @@ -36,7 +37,7 @@ async def add_model_class_to_graph( if hasattr(field_type, "model_fields"): # Check if field type is a Pydantic model await add_model_class_to_graph(field_type, graph, model_name, field_name) - elif get_origin(field.annotation) == list: + elif isinstance(get_origin(field.annotation), list): list_types = get_args(field_type) for item_type in list_types: await add_model_class_to_graph(item_type, graph, model_name, field_name) @@ -45,26 +46,26 @@ async def add_model_class_to_graph( if hasattr(item_type, "model_fields"): await add_model_class_to_graph(item_type, graph, model_name, field_name) else: - await graph.add_node(str(item_type), dict(type = "value")) + await graph.add_node(str(item_type), dict(type="value")) await graph.add_edge( model_name, str(item_type), field_name, dict( - relationship_name = field_name, - source_node_id = model_name, - target_node_id = str(item_type), + relationship_name=field_name, + source_node_id=model_name, + target_node_id=str(item_type), ), ) else: - await graph.add_node(str(field_type), dict(type = "value")) + await graph.add_node(str(field_type), dict(type="value")) await graph.add_edge( model_name, str(field_type), field_name, dict( - relationship_name = field_name, - source_node_id = model_name, - target_node_id = str(field_type), + relationship_name=field_name, + source_node_id=model_name, + target_node_id=str(field_type), ), ) diff --git a/cognee/modules/data/extraction/knowledge_graph/extract_content_graph.py b/cognee/modules/data/extraction/knowledge_graph/extract_content_graph.py index e52f3992a..49af7351a 100644 --- a/cognee/modules/data/extraction/knowledge_graph/extract_content_graph.py +++ b/cognee/modules/data/extraction/knowledge_graph/extract_content_graph.py @@ -3,10 +3,13 @@ from cognee.infrastructure.llm.get_llm_client import get_llm_client from cognee.infrastructure.llm.prompts import render_prompt + async def extract_content_graph(content: str, response_model: Type[BaseModel]): llm_client = get_llm_client() system_prompt = render_prompt("generate_graph_prompt.txt", {}) - content_graph = await llm_client.acreate_structured_output(content, system_prompt, response_model) + content_graph = await llm_client.acreate_structured_output( + content, system_prompt, response_model + ) return content_graph diff --git a/cognee/modules/data/methods/__init__.py b/cognee/modules/data/methods/__init__.py index 34f943359..c32db1d2f 100644 --- a/cognee/modules/data/methods/__init__.py +++ b/cognee/modules/data/methods/__init__.py @@ -10,4 +10,4 @@ # Delete from .delete_dataset import delete_dataset -from .delete_data import delete_data \ No newline at end of file +from .delete_data import delete_data diff --git a/cognee/modules/data/methods/create_dataset.py b/cognee/modules/data/methods/create_dataset.py index aa9c645f7..be4ea8792 100644 --- a/cognee/modules/data/methods/create_dataset.py +++ b/cognee/modules/data/methods/create_dataset.py @@ -4,20 +4,19 @@ from sqlalchemy.orm import joinedload from cognee.modules.data.models import Dataset + async def create_dataset(dataset_name: str, owner_id: UUID, session: AsyncSession) -> Dataset: - dataset = (await session.scalars( - select(Dataset)\ - .options(joinedload(Dataset.data))\ + dataset = ( + await session.scalars( + select(Dataset) + .options(joinedload(Dataset.data)) .filter(Dataset.name == dataset_name) .filter(Dataset.owner_id == owner_id) - )).first() + ) + ).first() if dataset is None: - dataset = Dataset( - id = uuid5(NAMESPACE_OID, dataset_name), - name = dataset_name, - data = [] - ) + dataset = Dataset(id=uuid5(NAMESPACE_OID, dataset_name), name=dataset_name, data=[]) dataset.owner_id = owner_id session.add(dataset) diff --git a/cognee/modules/data/methods/delete_data.py b/cognee/modules/data/methods/delete_data.py index 65abe714a..2d87d73a5 100644 --- a/cognee/modules/data/methods/delete_data.py +++ b/cognee/modules/data/methods/delete_data.py @@ -6,14 +6,16 @@ async def delete_data(data: Data): """Delete a data record from the database. - Args: - data (Data): The data object to be deleted. + Args: + data (Data): The data object to be deleted. - Raises: - ValueError: If the data object is invalid. + Raises: + ValueError: If the data object is invalid. """ - if not hasattr(data, '__tablename__'): - raise InvalidAttributeError(message="The provided data object is missing the required '__tablename__' attribute.") + if not hasattr(data, "__tablename__"): + raise InvalidAttributeError( + message="The provided data object is missing the required '__tablename__' attribute." + ) db_engine = get_relational_engine() diff --git a/cognee/modules/data/methods/delete_dataset.py b/cognee/modules/data/methods/delete_dataset.py index 96a2e7d71..ff20ff9e7 100644 --- a/cognee/modules/data/methods/delete_dataset.py +++ b/cognee/modules/data/methods/delete_dataset.py @@ -1,6 +1,7 @@ from cognee.modules.data.models import Dataset from cognee.infrastructure.databases.relational import get_relational_engine + async def delete_dataset(dataset: Dataset): db_engine = get_relational_engine() diff --git a/cognee/modules/data/methods/get_data.py b/cognee/modules/data/methods/get_data.py index d7daff29b..d1fff2a0a 100644 --- a/cognee/modules/data/methods/get_data.py +++ b/cognee/modules/data/methods/get_data.py @@ -4,15 +4,16 @@ from ..exceptions import UnauthorizedDataAccessError from ..models import Data + async def get_data(user_id: UUID, data_id: UUID) -> Optional[Data]: """Retrieve data by ID. - Args: - user_id (UUID): user ID - data_id (UUID): ID of the data to retrieve + Args: + user_id (UUID): user ID + data_id (UUID): ID of the data to retrieve - Returns: - Optional[Data]: The requested data object if found, None otherwise + Returns: + Optional[Data]: The requested data object if found, None otherwise """ db_engine = get_relational_engine() @@ -20,6 +21,8 @@ async def get_data(user_id: UUID, data_id: UUID) -> Optional[Data]: data = await session.get(Data, data_id) if data and data.owner_id != user_id: - raise UnauthorizedDataAccessError(message=f"User {user_id} is not authorized to access data {data_id}") + raise UnauthorizedDataAccessError( + message=f"User {user_id} is not authorized to access data {data_id}" + ) - return data \ No newline at end of file + return data diff --git a/cognee/modules/data/methods/get_dataset.py b/cognee/modules/data/methods/get_dataset.py index 9f46fa223..e5f7e9923 100644 --- a/cognee/modules/data/methods/get_dataset.py +++ b/cognee/modules/data/methods/get_dataset.py @@ -3,6 +3,7 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models import Dataset + async def get_dataset(user_id: UUID, dataset_id: UUID) -> Optional[Dataset]: db_engine = get_relational_engine() diff --git a/cognee/modules/data/methods/get_dataset_data.py b/cognee/modules/data/methods/get_dataset_data.py index f1b969b2d..80669fddb 100644 --- a/cognee/modules/data/methods/get_dataset_data.py +++ b/cognee/modules/data/methods/get_dataset_data.py @@ -3,6 +3,7 @@ from cognee.modules.data.models import Data, Dataset from cognee.infrastructure.databases.relational import get_relational_engine + async def get_dataset_data(dataset_id: UUID) -> list[Data]: db_engine = get_relational_engine() diff --git a/cognee/modules/data/methods/get_datasets.py b/cognee/modules/data/methods/get_datasets.py index 040c4c590..f6db585df 100644 --- a/cognee/modules/data/methods/get_datasets.py +++ b/cognee/modules/data/methods/get_datasets.py @@ -3,12 +3,13 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models import Dataset + async def get_datasets(user_id: UUID) -> list[Dataset]: db_engine = get_relational_engine() async with db_engine.get_async_session() as session: - datasets = (await session.scalars( - select(Dataset).filter(Dataset.owner_id == user_id) - )).all() + datasets = ( + await session.scalars(select(Dataset).filter(Dataset.owner_id == user_id)) + ).all() return datasets diff --git a/cognee/modules/data/methods/get_datasets_by_name.py b/cognee/modules/data/methods/get_datasets_by_name.py index f562268ce..be0613792 100644 --- a/cognee/modules/data/methods/get_datasets_by_name.py +++ b/cognee/modules/data/methods/get_datasets_by_name.py @@ -3,16 +3,19 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models import Dataset + async def get_datasets_by_name(dataset_names: list[str], user_id: UUID) -> list[Dataset]: db_engine = get_relational_engine() async with db_engine.get_async_session() as session: if isinstance(dataset_names, str): dataset_names = [dataset_names] - datasets = (await session.scalars( - select(Dataset) + datasets = ( + await session.scalars( + select(Dataset) .filter(Dataset.owner_id == user_id) .filter(Dataset.name.in_(dataset_names)) - )).all() + ) + ).all() return datasets diff --git a/cognee/modules/data/models/Data.py b/cognee/modules/data/models/Data.py index e24bc7c5c..cf8918db7 100644 --- a/cognee/modules/data/models/Data.py +++ b/cognee/modules/data/models/Data.py @@ -9,6 +9,7 @@ from .DatasetData import DatasetData from .Metadata import Metadata + class Data(Base): __tablename__ = "data" @@ -20,12 +21,8 @@ class Data(Base): raw_data_location = Column(String) owner_id = Column(UUID, index=True) content_hash = Column(String) - created_at = Column( - DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) - ) - updated_at = Column( - DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc) - ) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) datasets = relationship( "Dataset", @@ -42,7 +39,6 @@ class Data(Base): cascade="all, delete", ) - def to_json(self) -> dict: return { "id": str(self.id), diff --git a/cognee/modules/data/models/Dataset.py b/cognee/modules/data/models/Dataset.py index f7078b8f1..dc2a81a20 100644 --- a/cognee/modules/data/models/Dataset.py +++ b/cognee/modules/data/models/Dataset.py @@ -6,24 +6,25 @@ from cognee.infrastructure.databases.relational import Base from .DatasetData import DatasetData + class Dataset(Base): __tablename__ = "datasets" - id = Column(UUID, primary_key = True, default = uuid4) + id = Column(UUID, primary_key=True, default=uuid4) name = Column(Text) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime(timezone = True), onupdate = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) - owner_id = Column(UUID, index = True) + owner_id = Column(UUID, index=True) data: Mapped[List["Data"]] = relationship( "Data", - secondary = DatasetData.__tablename__, - back_populates = "datasets", - lazy = "noload", - cascade="all, delete" + secondary=DatasetData.__tablename__, + back_populates="datasets", + lazy="noload", + cascade="all, delete", ) def to_json(self) -> dict: @@ -33,5 +34,5 @@ def to_json(self) -> dict: "createdAt": self.created_at.isoformat(), "updatedAt": self.updated_at.isoformat() if self.updated_at else None, "ownerId": str(self.owner_id), - "data": [data.to_json() for data in self.data] + "data": [data.to_json() for data in self.data], } diff --git a/cognee/modules/data/models/DatasetData.py b/cognee/modules/data/models/DatasetData.py index a35c120eb..209771581 100644 --- a/cognee/modules/data/models/DatasetData.py +++ b/cognee/modules/data/models/DatasetData.py @@ -2,10 +2,11 @@ from sqlalchemy import Column, DateTime, ForeignKey, UUID from cognee.infrastructure.databases.relational import Base + class DatasetData(Base): __tablename__ = "dataset_data" - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) - dataset_id = Column(UUID, ForeignKey("datasets.id", ondelete="CASCADE"), primary_key = True) - data_id = Column(UUID, ForeignKey("data.id", ondelete="CASCADE"), primary_key = True) + dataset_id = Column(UUID, ForeignKey("datasets.id", ondelete="CASCADE"), primary_key=True) + data_id = Column(UUID, ForeignKey("data.id", ondelete="CASCADE"), primary_key=True) diff --git a/cognee/modules/data/models/Metadata.py b/cognee/modules/data/models/Metadata.py index 3ab30b38d..ab41d94be 100644 --- a/cognee/modules/data/models/Metadata.py +++ b/cognee/modules/data/models/Metadata.py @@ -14,13 +14,8 @@ class Metadata(Base): metadata_repr = Column(String) metadata_source = Column(String) - created_at = Column( - DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) - ) - updated_at = Column( - DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc) - ) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) - data_id = Column(UUID, ForeignKey("data.id", ondelete="CASCADE"), primary_key = False) + data_id = Column(UUID, ForeignKey("data.id", ondelete="CASCADE"), primary_key=False) data = relationship("Data", back_populates="metadata_relationship") - diff --git a/cognee/modules/data/operations/detect_language.py b/cognee/modules/data/operations/detect_language.py index e82675736..27fb853c6 100644 --- a/cognee/modules/data/operations/detect_language.py +++ b/cognee/modules/data/operations/detect_language.py @@ -2,6 +2,7 @@ logger = logging.getLogger(__name__) + async def detect_language(text: str): """ Detect the language of the given text and return its ISO 639-1 language code. @@ -14,6 +15,7 @@ async def detect_language(text: str): """ from langdetect import detect, LangDetectException + # Trim the text to the first 100 characters trimmed_text = text[:100] diff --git a/cognee/modules/data/operations/get_metadata.py b/cognee/modules/data/operations/get_metadata.py index 26637e383..f827c47c3 100644 --- a/cognee/modules/data/operations/get_metadata.py +++ b/cognee/modules/data/operations/get_metadata.py @@ -15,5 +15,3 @@ async def get_metadata(metadata_id: UUID) -> Metadata: metadata = await session.get(Metadata, metadata_id) return metadata - - diff --git a/cognee/modules/data/operations/translate_text.py b/cognee/modules/data/operations/translate_text.py index d8c27e42a..d9a7c9505 100644 --- a/cognee/modules/data/operations/translate_text.py +++ b/cognee/modules/data/operations/translate_text.py @@ -4,7 +4,10 @@ logger = logging.getLogger(__name__) -async def translate_text(text, source_language: str = "sr", target_language: str = "en", region_name = "eu-west-1"): + +async def translate_text( + text, source_language: str = "sr", target_language: str = "en", region_name="eu-west-1" +): """ Translate text from source language to target language using AWS Translate. Parameters: @@ -26,11 +29,11 @@ async def translate_text(text, source_language: str = "sr", target_language: str raise InvalidValueError(message="Source and target language codes are required.") try: - translate = boto3.client(service_name = "translate", region_name = region_name, use_ssl = True) + translate = boto3.client(service_name="translate", region_name=region_name, use_ssl=True) result = translate.translate_text( - Text = text, - SourceLanguageCode = source_language, - TargetLanguageCode = target_language, + Text=text, + SourceLanguageCode=source_language, + TargetLanguageCode=target_language, ) yield result.get("TranslatedText", "No translation found.") diff --git a/cognee/modules/data/operations/write_metadata.py b/cognee/modules/data/operations/write_metadata.py index 67c8c0e45..3c2c839c2 100644 --- a/cognee/modules/data/operations/write_metadata.py +++ b/cognee/modules/data/operations/write_metadata.py @@ -11,11 +11,12 @@ from ..models.Metadata import Metadata -async def write_metadata(data_item: Union[BinaryIO, str, Any], data_id: UUID, file_metadata: FileMetadata) -> UUID: +async def write_metadata( + data_item: Union[BinaryIO, str, Any], data_id: UUID, file_metadata: FileMetadata +) -> UUID: metadata_dict = get_metadata_dict(data_item, file_metadata) db_engine = get_relational_engine() async with db_engine.get_async_session() as session: - metadata = ( await session.execute(select(Metadata).filter(Metadata.data_id == data_id)) ).scalar_one_or_none() @@ -29,14 +30,13 @@ async def write_metadata(data_item: Union[BinaryIO, str, Any], data_id: UUID, fi id=data_id, metadata_repr=json.dumps(metadata_dict), metadata_source=parse_type(type(data_item)), - data_id=data_id + data_id=data_id, ) session.add(metadata) await session.commit() - def parse_type(type_: Any) -> str: pattern = r".+'([\w_\.]+)'" match = re.search(pattern, str(type_)) @@ -46,11 +46,13 @@ def parse_type(type_: Any) -> str: raise Exception(f"type: {type_} could not be parsed") -def get_metadata_dict(data_item: Union[BinaryIO, str, Any], file_metadata: FileMetadata) -> dict[str, Any]: +def get_metadata_dict( + data_item: Union[BinaryIO, str, Any], file_metadata: FileMetadata +) -> dict[str, Any]: if isinstance(data_item, str): - return(file_metadata) + return file_metadata elif isinstance(data_item, BinaryIO): - return(file_metadata) + return file_metadata elif hasattr(data_item, "dict") and inspect.ismethod(getattr(data_item, "dict")): return {**file_metadata, **data_item.dict()} else: diff --git a/cognee/modules/data/processing/document_types/AudioDocument.py b/cognee/modules/data/processing/document_types/AudioDocument.py index 268338703..a33d4e7fc 100644 --- a/cognee/modules/data/processing/document_types/AudioDocument.py +++ b/cognee/modules/data/processing/document_types/AudioDocument.py @@ -2,19 +2,20 @@ from .Document import Document from .ChunkerMapping import ChunkerConfig + class AudioDocument(Document): type: str = "audio" def create_transcript(self): result = get_llm_client().create_transcript(self.raw_data_location) - return(result.text) + return result.text def read(self, chunk_size: int, chunker: str): # Transcribe the audio file - + text = self.create_transcript() chunker_func = ChunkerConfig.get_chunker(chunker) - chunker = chunker_func(self, chunk_size = chunk_size, get_text = lambda: [text]) + chunker = chunker_func(self, chunk_size=chunk_size, get_text=lambda: [text]) yield from chunker.read() diff --git a/cognee/modules/data/processing/document_types/ChunkerMapping.py b/cognee/modules/data/processing/document_types/ChunkerMapping.py index 14dbb8bb7..f9a251528 100644 --- a/cognee/modules/data/processing/document_types/ChunkerMapping.py +++ b/cognee/modules/data/processing/document_types/ChunkerMapping.py @@ -1,9 +1,8 @@ from cognee.modules.chunking.TextChunker import TextChunker + class ChunkerConfig: - chunker_mapping = { - "text_chunker": TextChunker - } + chunker_mapping = {"text_chunker": TextChunker} @classmethod def get_chunker(cls, chunker_name: str): @@ -12,4 +11,4 @@ def get_chunker(cls, chunker_name: str): raise NotImplementedError( f"Chunker '{chunker_name}' is not implemented. Available options: {list(cls.chunker_mapping.keys())}" ) - return chunker_class \ No newline at end of file + return chunker_class diff --git a/cognee/modules/data/processing/document_types/Document.py b/cognee/modules/data/processing/document_types/Document.py index 8d6a3dafb..08380e809 100644 --- a/cognee/modules/data/processing/document_types/Document.py +++ b/cognee/modules/data/processing/document_types/Document.py @@ -8,10 +8,7 @@ class Document(DataPoint): raw_data_location: str metadata_id: UUID mime_type: str - _metadata: dict = { - "index_fields": ["name"], - "type": "Document" - } + _metadata: dict = {"index_fields": ["name"], "type": "Document"} - def read(self, chunk_size: int, chunker = str) -> str: + def read(self, chunk_size: int, chunker=str) -> str: pass diff --git a/cognee/modules/data/processing/document_types/ImageDocument.py b/cognee/modules/data/processing/document_types/ImageDocument.py index 352486bd8..424cd059c 100644 --- a/cognee/modules/data/processing/document_types/ImageDocument.py +++ b/cognee/modules/data/processing/document_types/ImageDocument.py @@ -2,19 +2,19 @@ from .Document import Document from .ChunkerMapping import ChunkerConfig + class ImageDocument(Document): type: str = "image" - def transcribe_image(self): result = get_llm_client().transcribe_image(self.raw_data_location) - return(result.choices[0].message.content) + return result.choices[0].message.content def read(self, chunk_size: int, chunker: str): # Transcribe the image file text = self.transcribe_image() chunker_func = ChunkerConfig.get_chunker(chunker) - chunker = chunker_func(self, chunk_size = chunk_size, get_text = lambda: [text]) + chunker = chunker_func(self, chunk_size=chunk_size, get_text=lambda: [text]) yield from chunker.read() diff --git a/cognee/modules/data/processing/document_types/PdfDocument.py b/cognee/modules/data/processing/document_types/PdfDocument.py index 361214718..684fb428c 100644 --- a/cognee/modules/data/processing/document_types/PdfDocument.py +++ b/cognee/modules/data/processing/document_types/PdfDocument.py @@ -2,6 +2,7 @@ from .Document import Document from .ChunkerMapping import ChunkerConfig + class PdfDocument(Document): type: str = "pdf" @@ -14,7 +15,7 @@ def get_text(): yield page_text chunker_func = ChunkerConfig.get_chunker(chunker) - chunker = chunker_func(self, chunk_size = chunk_size, get_text = get_text) + chunker = chunker_func(self, chunk_size=chunk_size, get_text=get_text) yield from chunker.read() diff --git a/cognee/modules/data/processing/document_types/TextDocument.py b/cognee/modules/data/processing/document_types/TextDocument.py index 3952d9845..f993ff221 100644 --- a/cognee/modules/data/processing/document_types/TextDocument.py +++ b/cognee/modules/data/processing/document_types/TextDocument.py @@ -1,12 +1,13 @@ from .Document import Document from .ChunkerMapping import ChunkerConfig + class TextDocument(Document): type: str = "text" def read(self, chunk_size: int, chunker: str): def get_text(): - with open(self.raw_data_location, mode = "r", encoding = "utf-8") as file: + with open(self.raw_data_location, mode="r", encoding="utf-8") as file: while True: text = file.read(1024) @@ -17,6 +18,6 @@ def get_text(): chunker_func = ChunkerConfig.get_chunker(chunker) - chunker = chunker_func(self, chunk_size = chunk_size, get_text = get_text) + chunker = chunker_func(self, chunk_size=chunk_size, get_text=get_text) yield from chunker.read() diff --git a/cognee/modules/data/processing/document_types/UnstructuredDocument.py b/cognee/modules/data/processing/document_types/UnstructuredDocument.py index 62632cd08..cd5c72e3b 100644 --- a/cognee/modules/data/processing/document_types/UnstructuredDocument.py +++ b/cognee/modules/data/processing/document_types/UnstructuredDocument.py @@ -27,6 +27,6 @@ def get_text(): yield text - chunker = TextChunker(self, chunk_size = chunk_size, get_text = get_text) + chunker = TextChunker(self, chunk_size=chunk_size, get_text=get_text) yield from chunker.read() diff --git a/cognee/modules/data/processing/has_new_chunks.py b/cognee/modules/data/processing/has_new_chunks.py index e5302b626..fb21be603 100644 --- a/cognee/modules/data/processing/has_new_chunks.py +++ b/cognee/modules/data/processing/has_new_chunks.py @@ -1,7 +1,10 @@ from cognee.infrastructure.databases.vector import get_vector_engine from cognee.modules.chunking import DocumentChunk -async def has_new_chunks(data_chunks: list[DocumentChunk], collection_name: str) -> list[DocumentChunk]: + +async def has_new_chunks( + data_chunks: list[DocumentChunk], collection_name: str +) -> list[DocumentChunk]: vector_engine = get_vector_engine() if not await vector_engine.has_collection(collection_name): @@ -22,9 +25,10 @@ async def has_new_chunks(data_chunks: list[DocumentChunk], collection_name: str) existing_chunks_map = {chunk.id: chunk.payload for chunk in existing_chunks} new_data_chunks = [ - chunk for chunk in data_chunks \ - if chunk.chunk_id not in existing_chunks_map \ - or chunk.text != existing_chunks_map[chunk.chunk_id]["text"] + chunk + for chunk in data_chunks + if chunk.chunk_id not in existing_chunks_map + or chunk.text != existing_chunks_map[chunk.chunk_id]["text"] ] return len(new_data_chunks) > 0 diff --git a/cognee/modules/engine/models/Entity.py b/cognee/modules/engine/models/Entity.py index 16e0ca3d8..63a153bf2 100644 --- a/cognee/modules/engine/models/Entity.py +++ b/cognee/modules/engine/models/Entity.py @@ -8,7 +8,4 @@ class Entity(DataPoint): is_a: EntityType description: str - _metadata: dict = { - "index_fields": ["name"], - "type": "Entity" - } + _metadata: dict = {"index_fields": ["name"], "type": "Entity"} diff --git a/cognee/modules/engine/models/EntityType.py b/cognee/modules/engine/models/EntityType.py index d3cc54311..7225bb3ae 100644 --- a/cognee/modules/engine/models/EntityType.py +++ b/cognee/modules/engine/models/EntityType.py @@ -6,7 +6,4 @@ class EntityType(DataPoint): name: str description: str - _metadata: dict = { - "index_fields": ["name"], - "type": "EntityType" - } + _metadata: dict = {"index_fields": ["name"], "type": "EntityType"} diff --git a/cognee/modules/engine/utils/generate_node_id.py b/cognee/modules/engine/utils/generate_node_id.py index db086a19c..489a88875 100644 --- a/cognee/modules/engine/utils/generate_node_id.py +++ b/cognee/modules/engine/utils/generate_node_id.py @@ -1,4 +1,5 @@ from uuid import NAMESPACE_OID, uuid5 + def generate_node_id(node_id: str) -> str: return uuid5(NAMESPACE_OID, node_id.lower().replace(" ", "_").replace("'", "")) diff --git a/cognee/modules/graph/cognee_graph/CogneeAbstractGraph.py b/cognee/modules/graph/cognee_graph/CogneeAbstractGraph.py index 9a7fb677f..471977d4c 100644 --- a/cognee/modules/graph/cognee_graph/CogneeAbstractGraph.py +++ b/cognee/modules/graph/cognee_graph/CogneeAbstractGraph.py @@ -3,6 +3,7 @@ from cognee.modules.graph.cognee_graph.CogneeGraphElements import Node, Edge from cognee.infrastructure.databases.graph.graph_db_interface import GraphDBInterface + class CogneeAbstractGraph(ABC): """ Abstract base class for representing a graph structure. @@ -30,6 +31,8 @@ def get_edges(self, node_id: str) -> List[Edge]: pass @abstractmethod - async def project_graph_from_db(self, adapter: GraphDBInterface, directed: bool, dimension: int) -> None: + async def project_graph_from_db( + self, adapter: GraphDBInterface, directed: bool, dimension: int + ) -> None: """Project the graph structure from a database using the provided adapter.""" pass diff --git a/cognee/modules/graph/cognee_graph/CogneeGraph.py b/cognee/modules/graph/cognee_graph/CogneeGraph.py index dbfbc7bb7..279a73b19 100644 --- a/cognee/modules/graph/cognee_graph/CogneeGraph.py +++ b/cognee/modules/graph/cognee_graph/CogneeGraph.py @@ -52,7 +52,7 @@ def get_edges_from_node(self, node_id: str) -> List[Edge]: else: raise EntityNotFoundError(message=f"Node with id {node_id} does not exist.") - def get_edges(self)-> List[Edge]: + def get_edges(self) -> List[Edge]: return self.edges async def project_graph_from_db( @@ -60,12 +60,11 @@ async def project_graph_from_db( adapter: Union[GraphDBInterface], node_properties_to_project: List[str], edge_properties_to_project: List[str], - directed = True, - node_dimension = 1, - edge_dimension = 1, - memory_fragment_filter = [], + directed=True, + node_dimension=1, + edge_dimension=1, + memory_fragment_filter=[], ) -> None: - if node_dimension < 1 or edge_dimension < 1: raise InvalidValueError(message="Dimensions must be positive integers") @@ -73,7 +72,9 @@ async def project_graph_from_db( if len(memory_fragment_filter) == 0: nodes_data, edges_data = await adapter.get_graph_data() else: - nodes_data, edges_data = await adapter.get_filtered_graph_data(attribute_filters = memory_fragment_filter) + nodes_data, edges_data = await adapter.get_filtered_graph_data( + attribute_filters=memory_fragment_filter + ) if not nodes_data: raise EntityNotFoundError(message="No node data retrieved from the database.") @@ -88,17 +89,27 @@ async def project_graph_from_db( source_node = self.get_node(str(source_id)) target_node = self.get_node(str(target_id)) if source_node and target_node: - edge_attributes = {key: properties.get(key) for key in edge_properties_to_project} - edge_attributes['relationship_type'] = relationship_type - - edge = Edge(source_node, target_node, attributes=edge_attributes, directed=directed, dimension=edge_dimension) + edge_attributes = { + key: properties.get(key) for key in edge_properties_to_project + } + edge_attributes["relationship_type"] = relationship_type + + edge = Edge( + source_node, + target_node, + attributes=edge_attributes, + directed=directed, + dimension=edge_dimension, + ) self.add_edge(edge) source_node.add_skeleton_edge(edge) target_node.add_skeleton_edge(edge) else: - raise EntityNotFoundError(message=f"Edge references nonexistent nodes: {source_id} -> {target_id}") + raise EntityNotFoundError( + message=f"Edge references nonexistent nodes: {source_id} -> {target_id}" + ) except (ValueError, TypeError) as e: print(f"Error projecting graph: {e}") @@ -110,13 +121,15 @@ async def map_vector_distances_to_graph_nodes(self, node_distances) -> None: for scored_result in scored_results: node_id = str(scored_result.id) score = scored_result.score - node =self.get_node(node_id) + node = self.get_node(node_id) if node: node.add_attribute("vector_distance", score) else: print(f"Node with id {node_id} not found in the graph.") - async def map_vector_distances_to_graph_edges(self, vector_engine, query) -> None: # :TODO: When we calculate edge embeddings in vector db change this similarly to node mapping + async def map_vector_distances_to_graph_edges( + self, vector_engine, query + ) -> None: # :TODO: When we calculate edge embeddings in vector db change this similarly to node mapping try: # Step 1: Generate the query embedding query_vector = await vector_engine.embed_data([query]) @@ -127,7 +140,7 @@ async def map_vector_distances_to_graph_edges(self, vector_engine, query) -> Non # Step 2: Collect all unique relationship types unique_relationship_types = set() for edge in self.edges: - relationship_type = edge.attributes.get('relationship_type') + relationship_type = edge.attributes.get("relationship_type") if relationship_type: unique_relationship_types.add(relationship_type) @@ -137,12 +150,14 @@ async def map_vector_distances_to_graph_edges(self, vector_engine, query) -> Non # Step 4: Map relationship types to their embeddings and calculate distances embedding_map = {} - for relationship_type, embedding in zip(unique_relationship_types, relationship_type_embeddings): + for relationship_type, embedding in zip( + unique_relationship_types, relationship_type_embeddings + ): edge_vector = np.array(embedding) # Calculate cosine similarity similarity = np.dot(query_vector, edge_vector) / ( - np.linalg.norm(query_vector) * np.linalg.norm(edge_vector) + np.linalg.norm(query_vector) * np.linalg.norm(edge_vector) ) distance = 1 - similarity @@ -151,7 +166,7 @@ async def map_vector_distances_to_graph_edges(self, vector_engine, query) -> Non # Step 4: Assign precomputed distances to edges for edge in self.edges: - relationship_type = edge.attributes.get('relationship_type') + relationship_type = edge.attributes.get("relationship_type") if not relationship_type or relationship_type not in embedding_map: print(f"Edge {edge} has an unknown or missing relationship type.") continue @@ -162,7 +177,6 @@ async def map_vector_distances_to_graph_edges(self, vector_engine, query) -> Non except Exception as ex: print(f"Error mapping vector distances to edges: {ex}") - async def calculate_top_triplet_importances(self, k: int) -> List: min_heap = [] for i, edge in enumerate(self.edges): @@ -179,6 +193,4 @@ async def calculate_top_triplet_importances(self, k: int) -> List: if len(min_heap) > k: heapq.heappop(min_heap) - return [edge for _, _, edge in sorted(min_heap)] - diff --git a/cognee/modules/graph/cognee_graph/CogneeGraphElements.py b/cognee/modules/graph/cognee_graph/CogneeGraphElements.py index bab0f3bb6..33f02dc22 100644 --- a/cognee/modules/graph/cognee_graph/CogneeGraphElements.py +++ b/cognee/modules/graph/cognee_graph/CogneeGraphElements.py @@ -6,25 +6,28 @@ class Node: """ - Represents a node in a graph. - Attributes: - id (str): A unique identifier for the node. - attributes (Dict[str, Any]): A dictionary of attributes associated with the node. - neighbors (List[Node]): Represents the original nodes - skeleton_edges (List[Edge]): Represents the original edges - """ + Represents a node in a graph. + Attributes: + id (str): A unique identifier for the node. + attributes (Dict[str, Any]): A dictionary of attributes associated with the node. + neighbors (List[Node]): Represents the original nodes + skeleton_edges (List[Edge]): Represents the original edges + """ + id: str attributes: Dict[str, Any] skeleton_neighbours: List["Node"] skeleton_edges: List["Edge"] status: np.ndarray - def __init__(self, node_id: str, attributes: Optional[Dict[str, Any]] = None, dimension: int = 1): + def __init__( + self, node_id: str, attributes: Optional[Dict[str, Any]] = None, dimension: int = 1 + ): if dimension <= 0: raise InvalidValueError(message="Dimension must be a positive integer") self.id = node_id self.attributes = attributes if attributes is not None else {} - self.attributes["vector_distance"] = float('inf') + self.attributes["vector_distance"] = float("inf") self.skeleton_neighbours = [] self.skeleton_edges = [] self.status = np.ones(dimension, dtype=int) @@ -56,7 +59,9 @@ def remove_skeleton_edge(self, edge: "Edge") -> None: def is_node_alive_in_dimension(self, dimension: int) -> bool: if dimension < 0 or dimension >= len(self.status): - raise InvalidValueError(message=f"Dimension {dimension} is out of range. Valid range is 0 to {len(self.status) - 1}.") + raise InvalidValueError( + message=f"Dimension {dimension} is out of range. Valid range is 0 to {len(self.status) - 1}." + ) return self.status[dimension] == 1 def add_attribute(self, key: str, value: Any) -> None: @@ -83,12 +88,12 @@ def __eq__(self, other: "Node") -> bool: class Edge: """ - Represents an edge in a graph, connecting two nodes. - Attributes: - node1 (Node): The starting node of the edge. - node2 (Node): The ending node of the edge. - attributes (Dict[str, Any]): A dictionary of attributes associated with the edge. - directed (bool): A flag indicating whether the edge is directed or undirected. + Represents an edge in a graph, connecting two nodes. + Attributes: + node1 (Node): The starting node of the edge. + node2 (Node): The ending node of the edge. + attributes (Dict[str, Any]): A dictionary of attributes associated with the edge. + directed (bool): A flag indicating whether the edge is directed or undirected. """ node1: "Node" @@ -97,19 +102,28 @@ class Edge: directed: bool status: np.ndarray - def __init__(self, node1: "Node", node2: "Node", attributes: Optional[Dict[str, Any]] = None, directed: bool = True, dimension: int = 1): + def __init__( + self, + node1: "Node", + node2: "Node", + attributes: Optional[Dict[str, Any]] = None, + directed: bool = True, + dimension: int = 1, + ): if dimension <= 0: raise InvalidValueError(message="Dimensions must be a positive integer.") self.node1 = node1 self.node2 = node2 self.attributes = attributes if attributes is not None else {} - self.attributes["vector_distance"] = float('inf') + self.attributes["vector_distance"] = float("inf") self.directed = directed self.status = np.ones(dimension, dtype=int) def is_edge_alive_in_dimension(self, dimension: int) -> bool: if dimension < 0 or dimension >= len(self.status): - raise InvalidValueError(message=f"Dimension {dimension} is out of range. Valid range is 0 to {len(self.status) - 1}.") + raise InvalidValueError( + message=f"Dimension {dimension} is out of range. Valid range is 0 to {len(self.status) - 1}." + ) return self.status[dimension] == 1 def add_attribute(self, key: str, value: Any) -> None: @@ -140,4 +154,4 @@ def __eq__(self, other: "Edge") -> bool: if self.directed: return self.node1 == other.node1 and self.node2 == other.node2 else: - return {self.node1, self.node2} == {other.node1, other.node2} \ No newline at end of file + return {self.node1, self.node2} == {other.node1, other.node2} diff --git a/cognee/modules/graph/exceptions/__init__.py b/cognee/modules/graph/exceptions/__init__.py index e8330caf3..5cf600099 100644 --- a/cognee/modules/graph/exceptions/__init__.py +++ b/cognee/modules/graph/exceptions/__init__.py @@ -7,4 +7,4 @@ from .exceptions import ( EntityNotFoundError, EntityAlreadyExistsError, -) \ No newline at end of file +) diff --git a/cognee/modules/graph/exceptions/exceptions.py b/cognee/modules/graph/exceptions/exceptions.py index af15bb616..854e620ff 100644 --- a/cognee/modules/graph/exceptions/exceptions.py +++ b/cognee/modules/graph/exceptions/exceptions.py @@ -1,6 +1,7 @@ from cognee.exceptions import CogneeApiError from fastapi import status + class EntityNotFoundError(CogneeApiError): """Database returns nothing""" @@ -22,4 +23,4 @@ def __init__( name: str = "EntityAlreadyExistsError", status_code=status.HTTP_409_CONFLICT, ): - super().__init__(message, name, status_code) \ No newline at end of file + super().__init__(message, name, status_code) diff --git a/cognee/modules/graph/models/EdgeType.py b/cognee/modules/graph/models/EdgeType.py index 998f08d8d..36aec2e7c 100644 --- a/cognee/modules/graph/models/EdgeType.py +++ b/cognee/modules/graph/models/EdgeType.py @@ -8,7 +8,4 @@ class EdgeType(DataPoint): relationship_name: str number_of_edges: int - _metadata: dict = { - "index_fields": ["relationship_name"], - "type": "EdgeType" - } \ No newline at end of file + _metadata: dict = {"index_fields": ["relationship_name"], "type": "EdgeType"} diff --git a/cognee/modules/graph/utils/convert_node_to_data_point.py b/cognee/modules/graph/utils/convert_node_to_data_point.py index 602a7ffa3..b89e50db9 100644 --- a/cognee/modules/graph/utils/convert_node_to_data_point.py +++ b/cognee/modules/graph/utils/convert_node_to_data_point.py @@ -15,6 +15,7 @@ def get_all_subclasses(cls): return subclasses + def find_subclass_by_name(cls, name): for subclass in get_all_subclasses(cls): if subclass.__name__ == name: diff --git a/cognee/modules/graph/utils/deduplicate_nodes_and_edges.py b/cognee/modules/graph/utils/deduplicate_nodes_and_edges.py index e863960ea..f61d51f3e 100644 --- a/cognee/modules/graph/utils/deduplicate_nodes_and_edges.py +++ b/cognee/modules/graph/utils/deduplicate_nodes_and_edges.py @@ -1,5 +1,6 @@ from cognee.infrastructure.engine import DataPoint + def deduplicate_nodes_and_edges(nodes: list[DataPoint], edges: list[dict]): added_entities = {} final_nodes = [] diff --git a/cognee/modules/graph/utils/expand_with_nodes_and_edges.py b/cognee/modules/graph/utils/expand_with_nodes_and_edges.py index cfa2ad9b4..23a6f4f63 100644 --- a/cognee/modules/graph/utils/expand_with_nodes_and_edges.py +++ b/cognee/modules/graph/utils/expand_with_nodes_and_edges.py @@ -36,10 +36,10 @@ def expand_with_nodes_and_edges( if f"{str(type_node_id)}_type" not in added_nodes_map: type_node = EntityType( - id = type_node_id, - name = type_node_name, - type = type_node_name, - description = type_node_name, + id=type_node_id, + name=type_node_name, + type=type_node_name, + description=type_node_name, ) added_nodes_map[f"{str(type_node_id)}_type"] = type_node else: @@ -47,10 +47,10 @@ def expand_with_nodes_and_edges( if f"{str(node_id)}_entity" not in added_nodes_map: entity_node = Entity( - id = node_id, - name = node_name, - is_a = type_node, - description = node.description, + id=node_id, + name=node_name, + is_a=type_node, + description=node.description, ) if data_chunk.contains is None: @@ -75,11 +75,9 @@ def expand_with_nodes_and_edges( target_node_id, edge.relationship_name, dict( - relationship_name = generate_edge_name( - edge.relationship_name - ), - source_node_id = source_node_id, - target_node_id = target_node_id, + relationship_name=generate_edge_name(edge.relationship_name), + source_node_id=source_node_id, + target_node_id=target_node_id, ), ) ) diff --git a/cognee/modules/graph/utils/get_graph_from_model.py b/cognee/modules/graph/utils/get_graph_from_model.py index 1b7c0908b..f4d2ed77a 100644 --- a/cognee/modules/graph/utils/get_graph_from_model.py +++ b/cognee/modules/graph/utils/get_graph_from_model.py @@ -2,13 +2,14 @@ from cognee.infrastructure.engine import DataPoint from cognee.modules.storage.utils import copy_model + async def get_graph_from_model( data_point: DataPoint, added_nodes: dict, added_edges: dict, visited_properties: dict = None, - only_root = False, - include_root = True, + only_root=False, + include_root=True, ): if str(data_point.id) in added_nodes: return [], [] @@ -37,7 +38,11 @@ async def get_graph_from_model( continue - if isinstance(field_value, list) and len(field_value) > 0 and isinstance(field_value[0], DataPoint): + if ( + isinstance(field_value, list) + and len(field_value) > 0 + and isinstance(field_value[0], DataPoint) + ): excluded_properties.add(field_name) for index, item in enumerate(field_value): @@ -52,15 +57,14 @@ async def get_graph_from_model( data_point_properties[field_name] = field_value - if include_root and str(data_point.id) not in added_nodes: SimpleDataPointModel = copy_model( type(data_point), - include_fields = { + include_fields={ "_metadata": (dict, data_point._metadata), "__tablename__": (str, data_point.__tablename__), }, - exclude_fields = list(excluded_properties), + exclude_fields=list(excluded_properties), ) nodes.append(SimpleDataPointModel(**data_point_properties)) added_nodes[str(data_point.id)] = True @@ -79,12 +83,19 @@ async def get_graph_from_model( edge_key = str(data_point.id) + str(field_value.id) + field_name if str(edge_key) not in added_edges: - edges.append((data_point.id, field_value.id, field_name, { - "source_node_id": data_point.id, - "target_node_id": field_value.id, - "relationship_name": field_name, - "updated_at": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"), - })) + edges.append( + ( + data_point.id, + field_value.id, + field_name, + { + "source_node_id": data_point.id, + "target_node_id": field_value.id, + "relationship_name": field_name, + "updated_at": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"), + }, + ) + ) added_edges[str(edge_key)] = True if str(field_value.id) in added_nodes or only_root: @@ -92,10 +103,10 @@ async def get_graph_from_model( property_nodes, property_edges = await get_graph_from_model( field_value, - include_root = True, - added_nodes = added_nodes, - added_edges = added_edges, - visited_properties = visited_properties, + include_root=True, + added_nodes=added_nodes, + added_edges=added_edges, + visited_properties=visited_properties, ) for node in property_nodes: diff --git a/cognee/modules/graph/utils/get_model_instance_from_graph.py b/cognee/modules/graph/utils/get_model_instance_from_graph.py index 82cdfa150..abe378f51 100644 --- a/cognee/modules/graph/utils/get_model_instance_from_graph.py +++ b/cognee/modules/graph/utils/get_model_instance_from_graph.py @@ -18,12 +18,16 @@ def get_model_instance_from_graph(nodes: list[DataPoint], edges: list, entity_id edge_type = edge_metadata.get("type") if edge_type == "list": - NewModel = copy_model(type(source_node), { edge_label: (list[type(target_node)], PydanticUndefined) }) + NewModel = copy_model( + type(source_node), {edge_label: (list[type(target_node)], PydanticUndefined)} + ) - node_map[edge[0]] = NewModel(**source_node.model_dump(), **{ edge_label: [target_node] }) + node_map[edge[0]] = NewModel(**source_node.model_dump(), **{edge_label: [target_node]}) else: - NewModel = copy_model(type(source_node), { edge_label: (type(target_node), PydanticUndefined) }) + NewModel = copy_model( + type(source_node), {edge_label: (type(target_node), PydanticUndefined)} + ) - node_map[edge[0]] = NewModel(**source_node.model_dump(), **{ edge_label: target_node }) + node_map[edge[0]] = NewModel(**source_node.model_dump(), **{edge_label: target_node}) return node_map[entity_id] diff --git a/cognee/modules/graph/utils/retrieve_existing_edges.py b/cognee/modules/graph/utils/retrieve_existing_edges.py index 0959e79d8..637317a8f 100644 --- a/cognee/modules/graph/utils/retrieve_existing_edges.py +++ b/cognee/modules/graph/utils/retrieve_existing_edges.py @@ -22,18 +22,12 @@ async def retrieve_existing_edges( entity_node_id = generate_node_id(node.id) if str(type_node_id) not in processed_nodes: - type_node_edges.append( - (data_chunk.id, type_node_id, "exists_in") - ) + type_node_edges.append((data_chunk.id, type_node_id, "exists_in")) processed_nodes[str(type_node_id)] = True if str(entity_node_id) not in processed_nodes: - entity_node_edges.append( - (data_chunk.id, entity_node_id, "mentioned_in") - ) - type_entity_edges.append( - (entity_node_id, type_node_id, "is_a") - ) + entity_node_edges.append((data_chunk.id, entity_node_id, "mentioned_in")) + type_entity_edges.append((entity_node_id, type_node_id, "is_a")) processed_nodes[str(entity_node_id)] = True graph_node_edges = [ diff --git a/cognee/modules/ingestion/classify.py b/cognee/modules/ingestion/classify.py index dbb191cc3..8c428cbb9 100644 --- a/cognee/modules/ingestion/classify.py +++ b/cognee/modules/ingestion/classify.py @@ -13,4 +13,6 @@ def classify(data: Union[str, BinaryIO], filename: str = None): if isinstance(data, BufferedReader) or isinstance(data, SpooledTemporaryFile): return BinaryData(data, data.name.split("/")[-1] if data.name else filename) - raise IngestionError(message=f"Type of data sent to classify(data: Union[str, BinaryIO) not supported: {type(data)}") + raise IngestionError( + message=f"Type of data sent to classify(data: Union[str, BinaryIO) not supported: {type(data)}" + ) diff --git a/cognee/modules/ingestion/data_types/BinaryData.py b/cognee/modules/ingestion/data_types/BinaryData.py index 0606250ea..7f137a6a0 100644 --- a/cognee/modules/ingestion/data_types/BinaryData.py +++ b/cognee/modules/ingestion/data_types/BinaryData.py @@ -2,9 +2,11 @@ from cognee.infrastructure.files import get_file_metadata, FileMetadata from .IngestionData import IngestionData + def create_binary_data(data: BinaryIO): return BinaryData(data) + class BinaryData(IngestionData): name: str = None data: BinaryIO = None diff --git a/cognee/modules/ingestion/data_types/TextData.py b/cognee/modules/ingestion/data_types/TextData.py index 9f10b0d54..b518d54a9 100644 --- a/cognee/modules/ingestion/data_types/TextData.py +++ b/cognee/modules/ingestion/data_types/TextData.py @@ -2,9 +2,11 @@ from cognee.infrastructure.data.utils.extract_keywords import extract_keywords from .IngestionData import IngestionData + def create_text_data(data: str): return TextData(data) + class TextData(IngestionData): data: str = None metadata = None diff --git a/cognee/modules/ingestion/discover_directory_datasets.py b/cognee/modules/ingestion/discover_directory_datasets.py index 1fdb573f0..861784c16 100644 --- a/cognee/modules/ingestion/discover_directory_datasets.py +++ b/cognee/modules/ingestion/discover_directory_datasets.py @@ -1,5 +1,6 @@ from os import path, listdir + def discover_directory_datasets(root_dir_path: str, parent_dir: str = None): datasets = {} @@ -7,7 +8,9 @@ def discover_directory_datasets(root_dir_path: str, parent_dir: str = None): if path.isdir(path.join(root_dir_path, file_or_dir)): dataset_name = file_or_dir if parent_dir is None else f"{parent_dir}.{file_or_dir}" - nested_datasets = discover_directory_datasets(path.join(root_dir_path, file_or_dir), dataset_name) + nested_datasets = discover_directory_datasets( + path.join(root_dir_path, file_or_dir), dataset_name + ) for dataset in nested_datasets.keys(): datasets[dataset] = nested_datasets[dataset] diff --git a/cognee/modules/ingestion/exceptions/__init__.py b/cognee/modules/ingestion/exceptions/__init__.py index 33d59e113..7efe94987 100644 --- a/cognee/modules/ingestion/exceptions/__init__.py +++ b/cognee/modules/ingestion/exceptions/__init__.py @@ -6,4 +6,4 @@ from .exceptions import ( IngestionError, -) \ No newline at end of file +) diff --git a/cognee/modules/ingestion/exceptions/exceptions.py b/cognee/modules/ingestion/exceptions/exceptions.py index 4901be110..08991a946 100644 --- a/cognee/modules/ingestion/exceptions/exceptions.py +++ b/cognee/modules/ingestion/exceptions/exceptions.py @@ -1,11 +1,12 @@ from cognee.exceptions import CogneeApiError from fastapi import status + class IngestionError(CogneeApiError): def __init__( - self, - message: str = "Type of data sent to classify not supported.", - name: str = "IngestionError", - status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, + self, + message: str = "Type of data sent to classify not supported.", + name: str = "IngestionError", + status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, ): - super().__init__(message, name, status_code) \ No newline at end of file + super().__init__(message, name, status_code) diff --git a/cognee/modules/ingestion/get_matched_datasets.py b/cognee/modules/ingestion/get_matched_datasets.py index 563d8016c..d1c41b0f6 100644 --- a/cognee/modules/ingestion/get_matched_datasets.py +++ b/cognee/modules/ingestion/get_matched_datasets.py @@ -1,5 +1,6 @@ from .discover_directory_datasets import discover_directory_datasets + def get_matched_datasets(data_path: str, dataset_name_to_match: str = None): datasets = discover_directory_datasets(data_path) diff --git a/cognee/modules/ingestion/save_data_to_file.py b/cognee/modules/ingestion/save_data_to_file.py index 1af6ab0aa..2e6ba7e15 100644 --- a/cognee/modules/ingestion/save_data_to_file.py +++ b/cognee/modules/ingestion/save_data_to_file.py @@ -5,6 +5,7 @@ from cognee.infrastructure.files.storage import LocalStorage from .classify import classify + def save_data_to_file(data: Union[str, BinaryIO], filename: str = None): base_config = get_base_config() data_directory_path = base_config.data_root_directory @@ -16,7 +17,7 @@ def save_data_to_file(data: Union[str, BinaryIO], filename: str = None): file_metadata = classified_data.get_metadata() if "name" not in file_metadata or file_metadata["name"] is None: - data_contents = classified_data.get_data().encode('utf-8') + data_contents = classified_data.get_data().encode("utf-8") hash_contents = hashlib.md5(data_contents).hexdigest() file_metadata["name"] = "text_" + hash_contents + ".txt" file_name = file_metadata["name"] diff --git a/cognee/modules/pipelines/models/Pipeline.py b/cognee/modules/pipelines/models/Pipeline.py index f4d20bb0f..6d97633f0 100644 --- a/cognee/modules/pipelines/models/Pipeline.py +++ b/cognee/modules/pipelines/models/Pipeline.py @@ -6,18 +6,19 @@ from .PipelineTask import PipelineTask from .Task import Task + class Pipeline(Base): __tablename__ = "pipelines" - id = Column(UUID, primary_key = True, default = uuid4) + id = Column(UUID, primary_key=True, default=uuid4) name = Column(String) - description = Column(Text, nullable = True) + description = Column(Text, nullable=True) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime(timezone = True), onupdate = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) tasks = Mapped[list["Task"]] = relationship( - secondary = PipelineTask.__tablename__, - back_populates = "pipeline", + secondary=PipelineTask.__tablename__, + back_populates="pipeline", ) diff --git a/cognee/modules/pipelines/models/PipelineRun.py b/cognee/modules/pipelines/models/PipelineRun.py index ab3498efe..c778066fe 100644 --- a/cognee/modules/pipelines/models/PipelineRun.py +++ b/cognee/modules/pipelines/models/PipelineRun.py @@ -4,19 +4,21 @@ from sqlalchemy import Column, DateTime, JSON, Enum, UUID from cognee.infrastructure.databases.relational import Base + class PipelineRunStatus(enum.Enum): DATASET_PROCESSING_STARTED = "DATASET_PROCESSING_STARTED" DATASET_PROCESSING_COMPLETED = "DATASET_PROCESSING_COMPLETED" DATASET_PROCESSING_ERRORED = "DATASET_PROCESSING_ERRORED" + class PipelineRun(Base): __tablename__ = "pipeline_runs" - id = Column(UUID, primary_key = True, default = uuid4) + id = Column(UUID, primary_key=True, default=uuid4) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) status = Column(Enum(PipelineRunStatus)) - run_id = Column(UUID, index = True) + run_id = Column(UUID, index=True) run_info = Column(JSON) diff --git a/cognee/modules/pipelines/models/PipelineTask.py b/cognee/modules/pipelines/models/PipelineTask.py index c6c7eb5e9..ca38fde48 100644 --- a/cognee/modules/pipelines/models/PipelineTask.py +++ b/cognee/modules/pipelines/models/PipelineTask.py @@ -2,10 +2,11 @@ from sqlalchemy import Column, DateTime, ForeignKey, UUID from cognee.infrastructure.databases.relational import Base + class PipelineTask(Base): __tablename__ = "pipeline_task" - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) - pipeline_id = Column("pipeline", UUID, ForeignKey("pipeline.id"), primary_key = True) - task_id = Column("task", UUID, ForeignKey("task.id"), primary_key = True) + pipeline_id = Column("pipeline", UUID, ForeignKey("pipeline.id"), primary_key=True) + task_id = Column("task", UUID, ForeignKey("task.id"), primary_key=True) diff --git a/cognee/modules/pipelines/models/Task.py b/cognee/modules/pipelines/models/Task.py index eba6508e3..bf960bf09 100644 --- a/cognee/modules/pipelines/models/Task.py +++ b/cognee/modules/pipelines/models/Task.py @@ -5,20 +5,20 @@ from cognee.infrastructure.databases.relational import Base, UUID from .PipelineTask import PipelineTask + class Task(Base): __tablename__ = "tasks" - id = Column(UUID, primary_key = True, default = uuid4) + id = Column(UUID, primary_key=True, default=uuid4) name = Column(String) - description = Column(Text, nullable = True) + description = Column(Text, nullable=True) executable = Column(Text) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime(timezone = True), onupdate = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) datasets: Mapped[list["Pipeline"]] = relationship( - secondary = PipelineTask.__tablename__, - back_populates = "task" + secondary=PipelineTask.__tablename__, back_populates="task" ) diff --git a/cognee/modules/pipelines/models/TaskRun.py b/cognee/modules/pipelines/models/TaskRun.py index bf2e2a9f8..4f5059cdb 100644 --- a/cognee/modules/pipelines/models/TaskRun.py +++ b/cognee/modules/pipelines/models/TaskRun.py @@ -3,14 +3,15 @@ from sqlalchemy import Column, DateTime, String, JSON from cognee.infrastructure.databases.relational import Base, UUID + class TaskRun(Base): __tablename__ = "task_runs" - id = Column(UUID, primary_key = True, default = uuid4) + id = Column(UUID, primary_key=True, default=uuid4) task_name = Column(String) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) status = Column(String) diff --git a/cognee/modules/pipelines/operations/get_pipeline_status.py b/cognee/modules/pipelines/operations/get_pipeline_status.py index 4249d3bf0..4f49cd544 100644 --- a/cognee/modules/pipelines/operations/get_pipeline_status.py +++ b/cognee/modules/pipelines/operations/get_pipeline_status.py @@ -4,29 +4,32 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models import PipelineRun + async def get_pipeline_status(pipeline_ids: list[UUID]): db_engine = get_relational_engine() async with db_engine.get_async_session() as session: - query = select( - PipelineRun, - func.row_number().over( - partition_by = PipelineRun.run_id, - order_by = PipelineRun.created_at.desc(), - ).label("rn") - ).filter(PipelineRun.run_id.in_(pipeline_ids)).subquery() + query = ( + select( + PipelineRun, + func.row_number() + .over( + partition_by=PipelineRun.run_id, + order_by=PipelineRun.created_at.desc(), + ) + .label("rn"), + ) + .filter(PipelineRun.run_id.in_(pipeline_ids)) + .subquery() + ) aliased_pipeline_run = aliased(PipelineRun, query) - latest_runs = ( - select(aliased_pipeline_run).filter(query.c.rn == 1) - ) - + latest_runs = select(aliased_pipeline_run).filter(query.c.rn == 1) + runs = (await session.execute(latest_runs)).scalars().all() - pipeline_statuses = { - str(run.run_id): run.status for run in runs - } + pipeline_statuses = {str(run.run_id): run.status for run in runs} return pipeline_statuses @@ -38,4 +41,4 @@ async def get_pipeline_status(pipeline_ids: list[UUID]): # ) t # WHERE rn = 1;""" - # return { dataset["data_id"]: dataset["status"] for dataset in datasets_statuses } + # return { dataset["data_id"]: dataset["status"] for dataset in datasets_statuses } diff --git a/cognee/modules/pipelines/operations/log_pipeline_status.py b/cognee/modules/pipelines/operations/log_pipeline_status.py index 94872dee4..c0f08cd2a 100644 --- a/cognee/modules/pipelines/operations/log_pipeline_status.py +++ b/cognee/modules/pipelines/operations/log_pipeline_status.py @@ -2,14 +2,17 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models.PipelineRun import PipelineRun + async def log_pipeline_status(run_id: UUID, status: str, run_info: dict): db_engine = get_relational_engine() async with db_engine.get_async_session() as session: - session.add(PipelineRun( - run_id = run_id, - status = status, - run_info = run_info, - )) + session.add( + PipelineRun( + run_id=run_id, + status=status, + run_info=run_info, + ) + ) await session.commit() diff --git a/cognee/modules/pipelines/operations/run_parallel.py b/cognee/modules/pipelines/operations/run_parallel.py index ab78b8300..d1774299b 100644 --- a/cognee/modules/pipelines/operations/run_parallel.py +++ b/cognee/modules/pipelines/operations/run_parallel.py @@ -2,6 +2,7 @@ import asyncio from ..tasks.Task import Task + def run_tasks_parallel(tasks: List[Task]) -> Callable[[Any], Generator[Any, Any, Any]]: async def parallel_run(*args, **kwargs): parallel_tasks = [asyncio.create_task(task.run(*args, **kwargs)) for task in tasks] diff --git a/cognee/modules/pipelines/operations/run_tasks.py b/cognee/modules/pipelines/operations/run_tasks.py index 35e32cf74..d5d7ef7a4 100644 --- a/cognee/modules/pipelines/operations/run_tasks.py +++ b/cognee/modules/pipelines/operations/run_tasks.py @@ -11,7 +11,8 @@ logger = logging.getLogger("run_tasks(tasks: [Task], data)") -async def run_tasks_base(tasks: list[Task], data = None, user: User = None): + +async def run_tasks_base(tasks: list[Task], data=None, user: User = None): if len(tasks) == 0: yield data return @@ -25,9 +26,13 @@ async def run_tasks_base(tasks: list[Task], data = None, user: User = None): if inspect.isasyncgenfunction(running_task.executable): logger.info("Async generator task started: `%s`", running_task.executable.__name__) - send_telemetry("Async Generator Task Started", user.id, { - "task_name": running_task.executable.__name__, - }) + send_telemetry( + "Async Generator Task Started", + user.id, + { + "task_name": running_task.executable.__name__, + }, + ) try: results = [] @@ -40,7 +45,7 @@ async def run_tasks_base(tasks: list[Task], data = None, user: User = None): async for result in run_tasks_base( leftover_tasks, results[0] if next_task_batch_size == 1 else results, - user = user, + user=user, ): yield result @@ -53,26 +58,38 @@ async def run_tasks_base(tasks: list[Task], data = None, user: User = None): results = [] logger.info("Async generator task completed: `%s`", running_task.executable.__name__) - send_telemetry("Async Generator Task Completed", user.id, { - "task_name": running_task.executable.__name__, - }) + send_telemetry( + "Async Generator Task Completed", + user.id, + { + "task_name": running_task.executable.__name__, + }, + ) except Exception as error: logger.error( "Async generator task errored: `%s`\n%s\n", running_task.executable.__name__, str(error), - exc_info = True, + exc_info=True, + ) + send_telemetry( + "Async Generator Task Errored", + user.id, + { + "task_name": running_task.executable.__name__, + }, ) - send_telemetry("Async Generator Task Errored", user.id, { - "task_name": running_task.executable.__name__, - }) raise error elif inspect.isgeneratorfunction(running_task.executable): logger.info("Generator task started: `%s`", running_task.executable.__name__) - send_telemetry("Generator Task Started", user.id, { - "task_name": running_task.executable.__name__, - }) + send_telemetry( + "Generator Task Started", + user.id, + { + "task_name": running_task.executable.__name__, + }, + ) try: results = [] @@ -80,7 +97,9 @@ async def run_tasks_base(tasks: list[Task], data = None, user: User = None): results.append(partial_result) if len(results) == next_task_batch_size: - async for result in run_tasks_base(leftover_tasks, results[0] if next_task_batch_size == 1 else results, user): + async for result in run_tasks_base( + leftover_tasks, results[0] if next_task_batch_size == 1 else results, user + ): yield result results = [] @@ -92,26 +111,38 @@ async def run_tasks_base(tasks: list[Task], data = None, user: User = None): results = [] logger.info("Generator task completed: `%s`", running_task.executable.__name__) - send_telemetry("Generator Task Completed", user_id = user.id, additional_properties = { - "task_name": running_task.executable.__name__, - }) + send_telemetry( + "Generator Task Completed", + user_id=user.id, + additional_properties={ + "task_name": running_task.executable.__name__, + }, + ) except Exception as error: logger.error( "Generator task errored: `%s`\n%s\n", running_task.executable.__name__, str(error), - exc_info = True, + exc_info=True, + ) + send_telemetry( + "Generator Task Errored", + user_id=user.id, + additional_properties={ + "task_name": running_task.executable.__name__, + }, ) - send_telemetry("Generator Task Errored", user_id = user.id, additional_properties = { - "task_name": running_task.executable.__name__, - }) raise error elif inspect.iscoroutinefunction(running_task.executable): logger.info("Coroutine task started: `%s`", running_task.executable.__name__) - send_telemetry("Coroutine Task Started", user_id = user.id, additional_properties = { - "task_name": running_task.executable.__name__, - }) + send_telemetry( + "Coroutine Task Started", + user_id=user.id, + additional_properties={ + "task_name": running_task.executable.__name__, + }, + ) try: task_result = await running_task.run(*args) @@ -119,26 +150,38 @@ async def run_tasks_base(tasks: list[Task], data = None, user: User = None): yield result logger.info("Coroutine task completed: `%s`", running_task.executable.__name__) - send_telemetry("Coroutine Task Completed", user.id, { - "task_name": running_task.executable.__name__, - }) + send_telemetry( + "Coroutine Task Completed", + user.id, + { + "task_name": running_task.executable.__name__, + }, + ) except Exception as error: logger.error( "Coroutine task errored: `%s`\n%s\n", running_task.executable.__name__, str(error), - exc_info = True, + exc_info=True, + ) + send_telemetry( + "Coroutine Task Errored", + user.id, + { + "task_name": running_task.executable.__name__, + }, ) - send_telemetry("Coroutine Task Errored", user.id, { - "task_name": running_task.executable.__name__, - }) raise error elif inspect.isfunction(running_task.executable): logger.info("Function task started: `%s`", running_task.executable.__name__) - send_telemetry("Function Task Started", user.id, { - "task_name": running_task.executable.__name__, - }) + send_telemetry( + "Function Task Started", + user.id, + { + "task_name": running_task.executable.__name__, + }, + ) try: task_result = running_task.run(*args) @@ -146,59 +189,78 @@ async def run_tasks_base(tasks: list[Task], data = None, user: User = None): yield result logger.info("Function task completed: `%s`", running_task.executable.__name__) - send_telemetry("Function Task Completed", user.id, { - "task_name": running_task.executable.__name__, - }) + send_telemetry( + "Function Task Completed", + user.id, + { + "task_name": running_task.executable.__name__, + }, + ) except Exception as error: logger.error( "Function task errored: `%s`\n%s\n", running_task.executable.__name__, str(error), - exc_info = True, + exc_info=True, + ) + send_telemetry( + "Function Task Errored", + user.id, + { + "task_name": running_task.executable.__name__, + }, ) - send_telemetry("Function Task Errored", user.id, { - "task_name": running_task.executable.__name__, - }) raise error -async def run_tasks_with_telemetry(tasks: list[Task], data, pipeline_name: str): +async def run_tasks_with_telemetry(tasks: list[Task], data, pipeline_name: str): config = get_current_settings() - - logger.debug("\nRunning pipeline with configuration:\n%s\n", json.dumps(config, indent = 1)) - + + logger.debug("\nRunning pipeline with configuration:\n%s\n", json.dumps(config, indent=1)) + user = await get_default_user() - + try: logger.info("Pipeline run started: `%s`", pipeline_name) - send_telemetry("Pipeline Run Started", - user.id, - additional_properties = {"pipeline_name": pipeline_name, } | config - ) - + send_telemetry( + "Pipeline Run Started", + user.id, + additional_properties={ + "pipeline_name": pipeline_name, + } + | config, + ) + async for result in run_tasks_base(tasks, data, user): yield result logger.info("Pipeline run completed: `%s`", pipeline_name) - send_telemetry("Pipeline Run Completed", - user.id, - additional_properties = {"pipeline_name": pipeline_name, } - ) + send_telemetry( + "Pipeline Run Completed", + user.id, + additional_properties={ + "pipeline_name": pipeline_name, + }, + ) except Exception as error: logger.error( "Pipeline run errored: `%s`\n%s\n", pipeline_name, str(error), - exc_info = True, + exc_info=True, + ) + send_telemetry( + "Pipeline Run Errored", + user.id, + additional_properties={ + "pipeline_name": pipeline_name, + } + | config, ) - send_telemetry("Pipeline Run Errored", - user.id, - additional_properties = {"pipeline_name": pipeline_name, } | config - ) raise error -async def run_tasks(tasks: list[Task], data = None, pipeline_name: str = "default_pipeline"): - + +async def run_tasks(tasks: list[Task], data=None, pipeline_name: str = "default_pipeline"): async for result in run_tasks_with_telemetry(tasks, data, pipeline_name): yield result diff --git a/cognee/modules/pipelines/tasks/Task.py b/cognee/modules/pipelines/tasks/Task.py index 7de60643a..753152d0d 100644 --- a/cognee/modules/pipelines/tasks/Task.py +++ b/cognee/modules/pipelines/tasks/Task.py @@ -1,6 +1,7 @@ from typing import Union, Callable, Any, Coroutine, Generator, AsyncGenerator -class Task(): + +class Task: executable: Union[ Callable[..., Any], Callable[..., Coroutine[Any, Any, Any]], @@ -12,12 +13,9 @@ class Task(): } default_params: dict[str, Any] = {} - def __init__(self, executable, *args, task_config = None, **kwargs): + def __init__(self, executable, *args, task_config=None, **kwargs): self.executable = executable - self.default_params = { - "args": args, - "kwargs": kwargs - } + self.default_params = {"args": args, "kwargs": kwargs} if task_config is not None: self.task_config = task_config @@ -27,6 +25,6 @@ def __init__(self, executable, *args, task_config = None, **kwargs): def run(self, *args, **kwargs): combined_args = args + self.default_params["args"] - combined_kwargs = { **self.default_params["kwargs"], **kwargs } + combined_kwargs = {**self.default_params["kwargs"], **kwargs} return self.executable(*combined_args, **combined_kwargs) diff --git a/cognee/modules/retrieval/brute_force_triplet_search.py b/cognee/modules/retrieval/brute_force_triplet_search.py index b5ee5b612..fdd312480 100644 --- a/cognee/modules/retrieval/brute_force_triplet_search.py +++ b/cognee/modules/retrieval/brute_force_triplet_search.py @@ -12,6 +12,7 @@ def format_triplets(edges): print("\n\n\n") + def filter_attributes(obj, attributes): """Helper function to filter out non-None properties, including nested dicts.""" result = {} @@ -20,7 +21,9 @@ def filter_attributes(obj, attributes): if value is not None: # If the value is a dict, extract relevant keys from it if isinstance(value, dict): - nested_values = {k: v for k, v in value.items() if k in attributes and v is not None} + nested_values = { + k: v for k, v in value.items() if k in attributes and v is not None + } result[attr] = nested_values else: result[attr] = value @@ -40,17 +43,15 @@ def filter_attributes(obj, attributes): edge_info = {key: value for key, value in edge_attributes.items() if value is not None} # Create the formatted triplet - triplet = ( - f"Node1: {node1_info}\n" - f"Edge: {edge_info}\n" - f"Node2: {node2_info}\n\n\n" - ) + triplet = f"Node1: {node1_info}\n" f"Edge: {edge_info}\n" f"Node2: {node2_info}\n\n\n" triplets.append(triplet) return "".join(triplets) -async def brute_force_triplet_search(query: str, user: User = None, top_k = 5, collections = None) -> list: +async def brute_force_triplet_search( + query: str, user: User = None, top_k=5, collections=None +) -> list: if user is None: user = await get_default_user() @@ -61,7 +62,9 @@ async def brute_force_triplet_search(query: str, user: User = None, top_k = 5, c return retrieved_results -def delete_duplicated_vector_db_elements(collections, results): #:TODO: This is just for now to fix vector db duplicates +def delete_duplicated_vector_db_elements( + collections, results +): #:TODO: This is just for now to fix vector db duplicates results_dict = {} for collection, results in zip(collections, results): seen_ids = set() @@ -78,22 +81,19 @@ def delete_duplicated_vector_db_elements(collections, results): #:TODO: This is async def brute_force_search( - query: str, - user: User, - top_k: int, - collections: List[str] = None + query: str, user: User, top_k: int, collections: List[str] = None ) -> list: """ - Performs a brute force search to retrieve the top triplets from the graph. + Performs a brute force search to retrieve the top triplets from the graph. - Args: - query (str): The search query. - user (User): The user performing the search. - top_k (int): The number of top results to retrieve. - collections (Optional[List[str]]): List of collections to query. Defaults to predefined collections. + Args: + query (str): The search query. + user (User): The user performing the search. + top_k (int): The number of top results to retrieve. + collections (Optional[List[str]]): List of collections to query. Defaults to predefined collections. - Returns: - list: The top triplet results. + Returns: + list: The top triplet results. """ if not query or not isinstance(query, str): raise ValueError("The query must be a non-empty string.") @@ -101,7 +101,12 @@ async def brute_force_search( raise ValueError("top_k must be a positive integer.") if collections is None: - collections = ["entity_name", "text_summary_text", "entity_type_name", "document_chunk_text"] + collections = [ + "entity_name", + "text_summary_text", + "entity_type_name", + "document_chunk_text", + ] try: vector_engine = get_vector_engine() @@ -114,7 +119,10 @@ async def brute_force_search( try: results = await asyncio.gather( - *[vector_engine.get_distance_from_collection_elements(collection, query_text=query) for collection in collections] + *[ + vector_engine.get_distance_from_collection_elements(collection, query_text=query) + for collection in collections + ] ) ############################################# :TODO: Change when vector db does not contain duplicates @@ -124,13 +132,11 @@ async def brute_force_search( memory_fragment = CogneeGraph() - await memory_fragment.project_graph_from_db(graph_engine, - node_properties_to_project=['id', - 'description', - 'name', - 'type', - 'text'], - edge_properties_to_project=['relationship_name']) + await memory_fragment.project_graph_from_db( + graph_engine, + node_properties_to_project=["id", "description", "name", "type", "text"], + edge_properties_to_project=["relationship_name"], + ) await memory_fragment.map_vector_distances_to_graph_nodes(node_distances=node_distances) @@ -145,6 +151,8 @@ async def brute_force_search( return results except Exception as e: - logging.error("Error during brute force search for user: %s, query: %s. Error: %s", user.id, query, e) + logging.error( + "Error during brute force search for user: %s, query: %s. Error: %s", user.id, query, e + ) send_telemetry("cognee.brute_force_triplet_search EXECUTION FAILED", user.id) raise RuntimeError("An error occurred during brute force search") from e diff --git a/cognee/modules/retrieval/description_to_codepart_search.py b/cognee/modules/retrieval/description_to_codepart_search.py index e1da9a43f..ecd187907 100644 --- a/cognee/modules/retrieval/description_to_codepart_search.py +++ b/cognee/modules/retrieval/description_to_codepart_search.py @@ -10,7 +10,7 @@ from cognee.shared.utils import send_telemetry -async def code_description_to_code_part_search(query: str, user: User = None, top_k = 2) -> list: +async def code_description_to_code_part_search(query: str, user: User = None, top_k=2) -> list: if user is None: user = await get_default_user() @@ -21,12 +21,7 @@ async def code_description_to_code_part_search(query: str, user: User = None, to return retrieved_codeparts - -async def code_description_to_code_part( - query: str, - user: User, - top_k: int -) -> List[str]: +async def code_description_to_code_part(query: str, user: User, top_k: int) -> List[str]: """ Maps a code description query to relevant code parts using a CodeGraph pipeline. @@ -55,12 +50,12 @@ async def code_description_to_code_part( raise RuntimeError("System initialization error. Please try again later.") from init_error send_telemetry("code_description_to_code_part_search EXECUTION STARTED", user.id) - logging.info("Search initiated by user %s with query: '%s' and top_k: %d", user.id, query, top_k) + logging.info( + "Search initiated by user %s with query: '%s' and top_k: %d", user.id, query, top_k + ) try: - results = await vector_engine.search( - "code_summary_text", query_text=query, limit=top_k - ) + results = await vector_engine.search("code_summary_text", query_text=query, limit=top_k) if not results: logging.warning("No results found for query: '%s' by user: %s", query, user.id) return [] @@ -68,8 +63,8 @@ async def code_description_to_code_part( memory_fragment = CogneeGraph() await memory_fragment.project_graph_from_db( graph_engine, - node_properties_to_project=['id', 'type', 'text', 'source_code'], - edge_properties_to_project=['relationship_name'] + node_properties_to_project=["id", "type", "text", "source_code"], + edge_properties_to_project=["relationship_name"], ) code_pieces_to_return = set() @@ -84,24 +79,32 @@ async def code_description_to_code_part( for code_file in node_to_search_from.get_skeleton_neighbours(): for code_file_edge in code_file.get_skeleton_edges(): - if code_file_edge.get_attribute('relationship_name') == 'contains': + if code_file_edge.get_attribute("relationship_name") == "contains": code_pieces_to_return.add(code_file_edge.get_destination_node()) - logging.info("Search completed for user: %s, query: '%s'. Found %d code pieces.", - user.id, query, len(code_pieces_to_return)) + logging.info( + "Search completed for user: %s, query: '%s'. Found %d code pieces.", + user.id, + query, + len(code_pieces_to_return), + ) return list(code_pieces_to_return) except Exception as exec_error: logging.error( "Error during code description to code part search for user: %s, query: '%s'. Error: %s", - user.id, query, exec_error, exc_info=True + user.id, + query, + exec_error, + exc_info=True, ) send_telemetry("code_description_to_code_part_search EXECUTION FAILED", user.id) raise RuntimeError("An error occurred while processing your request.") from exec_error if __name__ == "__main__": + async def main(): query = "I am looking for a class with blue eyes" user = None @@ -112,5 +115,3 @@ async def main(): print(f"An error occurred: {e}") asyncio.run(main()) - - diff --git a/cognee/modules/search/models/Query.py b/cognee/modules/search/models/Query.py index 182196333..ac19286b6 100644 --- a/cognee/modules/search/models/Query.py +++ b/cognee/modules/search/models/Query.py @@ -3,14 +3,15 @@ from sqlalchemy import Column, DateTime, String, UUID from cognee.infrastructure.databases.relational import Base + class Query(Base): __tablename__ = "queries" - id = Column(UUID, primary_key = True, default = uuid4) + id = Column(UUID, primary_key=True, default=uuid4) text = Column(String) query_type = Column(String) user_id = Column(UUID) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime(timezone = True), onupdate = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) diff --git a/cognee/modules/search/models/Result.py b/cognee/modules/search/models/Result.py index acda59ddb..cff097704 100644 --- a/cognee/modules/search/models/Result.py +++ b/cognee/modules/search/models/Result.py @@ -3,14 +3,15 @@ from sqlalchemy import Column, DateTime, Text, UUID from cognee.infrastructure.databases.relational import Base + class Result(Base): __tablename__ = "results" - id = Column(UUID, primary_key = True, default = uuid4) + id = Column(UUID, primary_key=True, default=uuid4) value = Column(Text) query_id = Column(UUID) - user_id = Column(UUID, index = True) + user_id = Column(UUID, index=True) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime(timezone = True), onupdate = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) diff --git a/cognee/modules/search/operations/get_history.py b/cognee/modules/search/operations/get_history.py index 831c4acc2..4ce6bac0c 100644 --- a/cognee/modules/search/operations/get_history.py +++ b/cognee/modules/search/operations/get_history.py @@ -4,24 +4,17 @@ from ..models.Query import Query from ..models.Result import Result + async def get_history(user_id: UUID, limit: int = 10) -> list[Result]: db_engine = get_relational_engine() queries_query = select( - Query.id, - Query.text.label("text"), - Query.created_at, - literal("user").label("user") - ) \ - .filter(Query.user_id == user_id) + Query.id, Query.text.label("text"), Query.created_at, literal("user").label("user") + ).filter(Query.user_id == user_id) results_query = select( - Result.id, - Result.value.label("text"), - Result.created_at, - literal("system").label("user") - ) \ - .filter(Result.user_id == user_id) + Result.id, Result.value.label("text"), Result.created_at, literal("system").label("user") + ).filter(Result.user_id == user_id) history_query = queries_query.union(results_query).order_by("created_at").limit(limit) diff --git a/cognee/modules/search/operations/get_queries.py b/cognee/modules/search/operations/get_queries.py index ded10a8e5..921dbfff3 100644 --- a/cognee/modules/search/operations/get_queries.py +++ b/cognee/modules/search/operations/get_queries.py @@ -3,15 +3,18 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models.Query import Query + async def get_queries(user_id: UUID, limit: int) -> list[Query]: db_engine = get_relational_engine() async with db_engine.get_async_session() as session: - queries = (await session.scalars( - select(Query) + queries = ( + await session.scalars( + select(Query) .filter(Query.user_id == user_id) .order_by(Query.created_at.desc()) .limit(limit) - )).all() + ) + ).all() return queries diff --git a/cognee/modules/search/operations/get_results.py b/cognee/modules/search/operations/get_results.py index 7f90a3f0f..a15cda44e 100644 --- a/cognee/modules/search/operations/get_results.py +++ b/cognee/modules/search/operations/get_results.py @@ -3,15 +3,18 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models.Result import Result + async def get_results(user_id: UUID, limit: int = 10) -> list[Result]: db_engine = get_relational_engine() async with db_engine.get_async_session() as session: - results = (await session.scalars( - select(Result) + results = ( + await session.scalars( + select(Result) .filter(Result.user_id == user_id) .order_by(Result.created_at.desc()) .limit(limit) - )).all() + ) + ).all() return results diff --git a/cognee/modules/search/operations/log_query.py b/cognee/modules/search/operations/log_query.py index 02ed3f157..d46a82890 100644 --- a/cognee/modules/search/operations/log_query.py +++ b/cognee/modules/search/operations/log_query.py @@ -2,14 +2,15 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models.Query import Query + async def log_query(query_text: str, query_type: str, user_id: UUID) -> Query: db_engine = get_relational_engine() async with db_engine.get_async_session() as session: query = Query( - text = query_text, - query_type = query_type, - user_id = user_id, + text=query_text, + query_type=query_type, + user_id=user_id, ) session.add(query) diff --git a/cognee/modules/search/operations/log_result.py b/cognee/modules/search/operations/log_result.py index b81e0b447..e03c45185 100644 --- a/cognee/modules/search/operations/log_result.py +++ b/cognee/modules/search/operations/log_result.py @@ -2,14 +2,17 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models.Result import Result + async def log_result(query_id: UUID, result: str, user_id: UUID): db_engine = get_relational_engine() async with db_engine.get_async_session() as session: - session.add(Result( - value = result, - query_id = query_id, - user_id = user_id, - )) + session.add( + Result( + value=result, + query_id=query_id, + user_id=user_id, + ) + ) await session.commit() diff --git a/cognee/modules/settings/get_current_settings.py b/cognee/modules/settings/get_current_settings.py index 3d6bad896..2fb1d3c81 100644 --- a/cognee/modules/settings/get_current_settings.py +++ b/cognee/modules/settings/get_current_settings.py @@ -4,28 +4,34 @@ from cognee.infrastructure.databases.vector import get_vectordb_config from cognee.infrastructure.databases.relational.config import get_relational_config + class LLMConfig(TypedDict): model: str provider: str + class VectorDBConfig(TypedDict): url: str provider: str + class GraphDBConfig(TypedDict): url: str provider: str + class RelationalConfig(TypedDict): url: str provider: str + class SettingsDict(TypedDict): llm: LLMConfig graph: GraphDBConfig vector: VectorDBConfig relational: RelationalConfig + def get_current_settings() -> SettingsDict: llm_config = get_llm_config() graph_config = get_graph_config() @@ -33,22 +39,22 @@ def get_current_settings() -> SettingsDict: relational_config = get_relational_config() return dict( - llm = { + llm={ "provider": llm_config.llm_provider, "model": llm_config.llm_model, }, - graph = { + graph={ "provider": graph_config.graph_database_provider, "url": graph_config.graph_database_url or graph_config.graph_file_path, }, - vector = { + vector={ "provider": vector_config.vector_db_provider, "url": vector_config.vector_db_url, }, - relational = { + relational={ "provider": relational_config.db_provider, - "url": f"{relational_config.db_host}:{relational_config.db_port}" \ - if relational_config.db_host \ - else f"{relational_config.db_path}/{relational_config.db_name}", + "url": f"{relational_config.db_host}:{relational_config.db_port}" + if relational_config.db_host + else f"{relational_config.db_path}/{relational_config.db_name}", }, ) diff --git a/cognee/modules/settings/get_settings.py b/cognee/modules/settings/get_settings.py index b67b9d6ab..93fd67cff 100644 --- a/cognee/modules/settings/get_settings.py +++ b/cognee/modules/settings/get_settings.py @@ -3,15 +3,18 @@ from cognee.infrastructure.databases.vector import get_vectordb_config from cognee.infrastructure.llm import get_llm_config + class ConfigChoice(BaseModel): value: str label: str + class ModelName(Enum): openai = "openai" ollama = "ollama" anthropic = "anthropic" - + + class LLMConfig(BaseModel): api_key: str model: ConfigChoice @@ -19,98 +22,130 @@ class LLMConfig(BaseModel): models: dict[str, list[ConfigChoice]] providers: list[ConfigChoice] + class VectorDBConfig(BaseModel): api_key: str url: str provider: ConfigChoice providers: list[ConfigChoice] + class SettingsDict(BaseModel): llm: LLMConfig vector_db: VectorDBConfig + def get_settings() -> SettingsDict: llm_config = get_llm_config() - vector_dbs = [{ - "value": "weaviate", - "label": "Weaviate", - }, { - "value": "qdrant", - "label": "Qdrant", - }, { - "value": "lancedb", - "label": "LanceDB", - }, { - "value": "pgvector", - "label": "PGVector", - }] + vector_dbs = [ + { + "value": "weaviate", + "label": "Weaviate", + }, + { + "value": "qdrant", + "label": "Qdrant", + }, + { + "value": "lancedb", + "label": "LanceDB", + }, + { + "value": "pgvector", + "label": "PGVector", + }, + ] vector_config = get_vectordb_config() - llm_providers = [{ - "value": "openai", - "label": "OpenAI", - }, { - "value": "ollama", - "label": "Ollama", - }, { - "value": "anthropic", - "label": "Anthropic", - }] - - return SettingsDict.model_validate(dict( - llm = { - "provider": { - "label": llm_config.llm_provider, - "value": llm_config.llm_provider, - } if llm_config.llm_provider else llm_providers[0], - "model": { - "value": llm_config.llm_model, - "label": llm_config.llm_model, - } if llm_config.llm_model else None, - "api_key": (llm_config.llm_api_key[:-10] + "**********") if llm_config.llm_api_key else None, - "providers": llm_providers, - "models": { - "openai": [{ - "value": "gpt-4o-mini", - "label": "gpt-4o-mini", - }, { - "value": "gpt-4o", - "label": "gpt-4o", - }, { - "value": "gpt-4-turbo", - "label": "gpt-4-turbo", - }, { - "value": "gpt-3.5-turbo", - "label": "gpt-3.5-turbo", - }], - "ollama": [{ - "value": "llama3", - "label": "llama3", - }, { - "value": "mistral", - "label": "mistral", - }], - "anthropic": [{ - "value": "Claude 3 Opus", - "label": "Claude 3 Opus", - }, { - "value": "Claude 3 Sonnet", - "label": "Claude 3 Sonnet", - }, { - "value": "Claude 3 Haiku", - "label": "Claude 3 Haiku", - }] - }, + llm_providers = [ + { + "value": "openai", + "label": "OpenAI", }, - vector_db = { - "provider": { - "label": vector_config.vector_db_provider, - "value": vector_config.vector_db_provider.lower(), - }, - "url": vector_config.vector_db_url, - "api_key": vector_config.vector_db_key, - "providers": vector_dbs, + { + "value": "ollama", + "label": "Ollama", }, - )) + { + "value": "anthropic", + "label": "Anthropic", + }, + ] + + return SettingsDict.model_validate( + dict( + llm={ + "provider": { + "label": llm_config.llm_provider, + "value": llm_config.llm_provider, + } + if llm_config.llm_provider + else llm_providers[0], + "model": { + "value": llm_config.llm_model, + "label": llm_config.llm_model, + } + if llm_config.llm_model + else None, + "api_key": (llm_config.llm_api_key[:-10] + "**********") + if llm_config.llm_api_key + else None, + "providers": llm_providers, + "models": { + "openai": [ + { + "value": "gpt-4o-mini", + "label": "gpt-4o-mini", + }, + { + "value": "gpt-4o", + "label": "gpt-4o", + }, + { + "value": "gpt-4-turbo", + "label": "gpt-4-turbo", + }, + { + "value": "gpt-3.5-turbo", + "label": "gpt-3.5-turbo", + }, + ], + "ollama": [ + { + "value": "llama3", + "label": "llama3", + }, + { + "value": "mistral", + "label": "mistral", + }, + ], + "anthropic": [ + { + "value": "Claude 3 Opus", + "label": "Claude 3 Opus", + }, + { + "value": "Claude 3 Sonnet", + "label": "Claude 3 Sonnet", + }, + { + "value": "Claude 3 Haiku", + "label": "Claude 3 Haiku", + }, + ], + }, + }, + vector_db={ + "provider": { + "label": vector_config.vector_db_provider, + "value": vector_config.vector_db_provider.lower(), + }, + "url": vector_config.vector_db_url, + "api_key": vector_config.vector_db_key, + "providers": vector_dbs, + }, + ) + ) diff --git a/cognee/modules/settings/save_llm_config.py b/cognee/modules/settings/save_llm_config.py index 83bb91d50..620d6bc32 100644 --- a/cognee/modules/settings/save_llm_config.py +++ b/cognee/modules/settings/save_llm_config.py @@ -1,11 +1,13 @@ from pydantic import BaseModel from cognee.infrastructure.llm import get_llm_config + class LLMConfig(BaseModel): api_key: str model: str provider: str + async def save_llm_config(new_llm_config: LLMConfig): llm_config = get_llm_config() diff --git a/cognee/modules/settings/save_vector_db_config.py b/cognee/modules/settings/save_vector_db_config.py index 1e0b683ea..7c48435a8 100644 --- a/cognee/modules/settings/save_vector_db_config.py +++ b/cognee/modules/settings/save_vector_db_config.py @@ -2,11 +2,13 @@ from pydantic import BaseModel from cognee.infrastructure.databases.vector import get_vectordb_config + class VectorDBConfig(BaseModel): url: str api_key: str provider: Union[Literal["lancedb"], Literal["qdrant"], Literal["weaviate"], Literal["pgvector"]] + async def save_vector_db_config(vector_db_config: VectorDBConfig): vector_config = get_vectordb_config() diff --git a/cognee/modules/storage/utils/__init__.py b/cognee/modules/storage/utils/__init__.py index a399e8a82..a877826db 100644 --- a/cognee/modules/storage/utils/__init__.py +++ b/cognee/modules/storage/utils/__init__.py @@ -2,9 +2,11 @@ from uuid import UUID from datetime import datetime from pydantic_core import PydanticUndefined +from pydantic import create_model from cognee.infrastructure.engine import DataPoint + class JSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): @@ -15,32 +17,30 @@ def default(self, obj): return json.JSONEncoder.default(self, obj) -from pydantic import create_model - def copy_model(model: DataPoint, include_fields: dict = {}, exclude_fields: list = []): fields = { name: (field.annotation, field.default if field.default is not None else PydanticUndefined) - for name, field in model.model_fields.items() - if name not in exclude_fields + for name, field in model.model_fields.items() + if name not in exclude_fields } - final_fields = { - **fields, - **include_fields - } + final_fields = {**fields, **include_fields} model = create_model(model.__name__, **final_fields) model.model_rebuild() return model + def get_own_properties(data_point: DataPoint): properties = {} for field_name, field_value in data_point: - if field_name == "_metadata" \ - or isinstance(field_value, dict) \ - or isinstance(field_value, DataPoint) \ - or (isinstance(field_value, list) and isinstance(field_value[0], DataPoint)): + if ( + field_name == "_metadata" + or isinstance(field_value, dict) + or isinstance(field_value, DataPoint) + or (isinstance(field_value, list) and isinstance(field_value[0], DataPoint)) + ): continue properties[field_name] = field_value diff --git a/cognee/modules/users/authentication/get_auth_backend.py b/cognee/modules/users/authentication/get_auth_backend.py index 3e5017ffa..c9d2b85b7 100644 --- a/cognee/modules/users/authentication/get_auth_backend.py +++ b/cognee/modules/users/authentication/get_auth_backend.py @@ -7,18 +7,19 @@ JWTStrategy, ) + @lru_cache def get_auth_backend(): - bearer_transport = BearerTransport(tokenUrl = "auth/jwt/login") + bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") def get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]: secret = os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret") - return JWTStrategy(secret, lifetime_seconds = 3600) + return JWTStrategy(secret, lifetime_seconds=3600) auth_backend = AuthenticationBackend( - name = "jwt", - transport = bearer_transport, - get_strategy = get_jwt_strategy, + name="jwt", + transport=bearer_transport, + get_strategy=get_jwt_strategy, ) return auth_backend diff --git a/cognee/modules/users/authentication/methods/authenticate_user.py b/cognee/modules/users/authentication/methods/authenticate_user.py index 5095092f4..026304695 100644 --- a/cognee/modules/users/authentication/methods/authenticate_user.py +++ b/cognee/modules/users/authentication/methods/authenticate_user.py @@ -4,6 +4,7 @@ from ...get_user_manager import get_user_manager_context from ...get_user_db import get_user_db_context + async def authenticate_user(email: str, password: str): try: relational_engine = get_relational_engine() @@ -11,7 +12,7 @@ async def authenticate_user(email: str, password: str): async with relational_engine.get_async_session() as session: async with get_user_db_context(session) as user_db: async with get_user_manager_context(user_db) as user_manager: - credentials = OAuth2PasswordRequestForm(username = email, password = password) + credentials = OAuth2PasswordRequestForm(username=email, password=password) user = await user_manager.authenticate(credentials) if user is None or not user.is_active: return None diff --git a/cognee/modules/users/exceptions/__init__.py b/cognee/modules/users/exceptions/__init__.py index 70e6e9d2a..8d1a9818c 100644 --- a/cognee/modules/users/exceptions/__init__.py +++ b/cognee/modules/users/exceptions/__init__.py @@ -8,4 +8,4 @@ GroupNotFoundError, UserNotFoundError, PermissionDeniedError, -) \ No newline at end of file +) diff --git a/cognee/modules/users/exceptions/exceptions.py b/cognee/modules/users/exceptions/exceptions.py index 7dda702db..3c27b01e5 100644 --- a/cognee/modules/users/exceptions/exceptions.py +++ b/cognee/modules/users/exceptions/exceptions.py @@ -28,9 +28,9 @@ def __init__( class PermissionDeniedError(CogneeApiError): def __init__( - self, - message: str = "User does not have permission on documents.", - name: str = "PermissionDeniedError", - status_code=status.HTTP_403_FORBIDDEN, + self, + message: str = "User does not have permission on documents.", + name: str = "PermissionDeniedError", + status_code=status.HTTP_403_FORBIDDEN, ): super().__init__(message, name, status_code) diff --git a/cognee/modules/users/get_fastapi_users.py b/cognee/modules/users/get_fastapi_users.py index 81abe3551..62cf7d692 100644 --- a/cognee/modules/users/get_fastapi_users.py +++ b/cognee/modules/users/get_fastapi_users.py @@ -6,6 +6,7 @@ from .get_user_manager import get_user_manager from .models.User import User + @lru_cache def get_fastapi_users(): auth_backend = get_auth_backend() diff --git a/cognee/modules/users/get_user_db.py b/cognee/modules/users/get_user_db.py index e77ae54b4..80a6f7ba3 100644 --- a/cognee/modules/users/get_user_db.py +++ b/cognee/modules/users/get_user_db.py @@ -1,17 +1,20 @@ from typing import AsyncGenerator from fastapi import Depends +from contextlib import asynccontextmanager from sqlalchemy.ext.asyncio import AsyncSession from fastapi_users.db import SQLAlchemyUserDatabase from cognee.infrastructure.databases.relational import get_relational_engine from .models.User import User + async def get_async_session() -> AsyncGenerator[AsyncSession, None]: db_engine = get_relational_engine() async with db_engine.get_async_session() as session: yield session + async def get_user_db(session: AsyncSession = Depends(get_async_session)): yield SQLAlchemyUserDatabase(session, User) -from contextlib import asynccontextmanager + get_user_db_context = asynccontextmanager(get_user_db) diff --git a/cognee/modules/users/get_user_manager.py b/cognee/modules/users/get_user_manager.py index 30410a985..a1bd9e174 100644 --- a/cognee/modules/users/get_user_manager.py +++ b/cognee/modules/users/get_user_manager.py @@ -4,7 +4,7 @@ from fastapi import Depends, Request from fastapi_users import BaseUserManager, UUIDIDMixin, models from fastapi_users.db import SQLAlchemyUserDatabase - +from contextlib import asynccontextmanager from .get_user_db import get_user_db from .models import User from .methods import get_user @@ -12,7 +12,9 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): - reset_password_token_secret = os.getenv("FASTAPI_USERS_RESET_PASSWORD_TOKEN_SECRET", "super_secret") + reset_password_token_secret = os.getenv( + "FASTAPI_USERS_RESET_PASSWORD_TOKEN_SECRET", "super_secret" + ) verification_token_secret = os.getenv("FASTAPI_USERS_VERIFICATION_TOKEN_SECRET", "super_secret") async def get(self, id: models.ID) -> models.UP: @@ -43,8 +45,9 @@ async def on_after_request_verify( ): print(f"Verification requested for user {user.id}. Verification token: {token}") + async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)): yield UserManager(user_db) -from contextlib import asynccontextmanager + get_user_manager_context = asynccontextmanager(get_user_manager) diff --git a/cognee/modules/users/methods/create_default_user.py b/cognee/modules/users/methods/create_default_user.py index 6cd8115a7..7000a2ad6 100644 --- a/cognee/modules/users/methods/create_default_user.py +++ b/cognee/modules/users/methods/create_default_user.py @@ -1,16 +1,17 @@ from .create_user import create_user + async def create_default_user(): default_user_email = "default_user@example.com" default_user_password = "default_password" user = await create_user( - email = default_user_email, - password = default_user_password, - is_superuser = False, - is_active = True, - is_verified = True, - auto_login = True, + email=default_user_email, + password=default_user_password, + is_superuser=False, + is_active=True, + is_verified=True, + auto_login=True, ) return user diff --git a/cognee/modules/users/methods/create_user.py b/cognee/modules/users/methods/create_user.py index cfe1dbd03..deae6fc8f 100644 --- a/cognee/modules/users/methods/create_user.py +++ b/cognee/modules/users/methods/create_user.py @@ -4,6 +4,7 @@ from ..get_user_db import get_user_db_context from ..models.User import UserCreate + async def create_user( email: str, password: str, @@ -20,11 +21,11 @@ async def create_user( async with get_user_manager_context(user_db) as user_manager: user = await user_manager.create( UserCreate( - email = email, - password = password, - is_superuser = is_superuser, - is_active = is_active, - is_verified = is_verified, + email=email, + password=password, + is_superuser=is_superuser, + is_active=is_active, + is_verified=is_verified, ) ) diff --git a/cognee/modules/users/methods/delete_user.py b/cognee/modules/users/methods/delete_user.py index dd0a585b6..3f92d815f 100644 --- a/cognee/modules/users/methods/delete_user.py +++ b/cognee/modules/users/methods/delete_user.py @@ -3,6 +3,7 @@ from ..get_user_manager import get_user_manager_context from ..get_user_db import get_user_db_context + async def delete_user(email: str): try: relational_engine = get_relational_engine() diff --git a/cognee/modules/users/methods/get_authenticated_user.py b/cognee/modules/users/methods/get_authenticated_user.py index cbc3c67ff..b088d14cb 100644 --- a/cognee/modules/users/methods/get_authenticated_user.py +++ b/cognee/modules/users/methods/get_authenticated_user.py @@ -2,4 +2,4 @@ fastapi_users = get_fastapi_users() -get_authenticated_user = fastapi_users.current_user(active = True, verified = True) +get_authenticated_user = fastapi_users.current_user(active=True, verified=True) diff --git a/cognee/modules/users/methods/get_default_user.py b/cognee/modules/users/methods/get_default_user.py index 9732515f3..c67d9d71f 100644 --- a/cognee/modules/users/methods/get_default_user.py +++ b/cognee/modules/users/methods/get_default_user.py @@ -4,12 +4,16 @@ from cognee.infrastructure.databases.relational import get_relational_engine from .create_default_user import create_default_user + async def get_default_user(): db_engine = get_relational_engine() async with db_engine.get_async_session() as session: - query = select(User).options(joinedload(User.groups))\ + query = ( + select(User) + .options(joinedload(User.groups)) .where(User.email == "default_user@example.com") + ) result = await session.execute(query) user = result.scalars().first() diff --git a/cognee/modules/users/methods/get_user.py b/cognee/modules/users/methods/get_user.py index bc7adb0b1..fe32491d8 100644 --- a/cognee/modules/users/methods/get_user.py +++ b/cognee/modules/users/methods/get_user.py @@ -4,12 +4,15 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ..models import User + async def get_user(user_id: UUID): db_engine = get_relational_engine() async with db_engine.get_async_session() as session: - user = (await session.execute( - select(User).options(joinedload(User.groups)).where(User.id == user_id) - )).scalar() + user = ( + await session.execute( + select(User).options(joinedload(User.groups)).where(User.id == user_id) + ) + ).scalar() return user diff --git a/cognee/modules/users/models/ACL.py b/cognee/modules/users/models/ACL.py index f54d24224..c920db5f3 100644 --- a/cognee/modules/users/models/ACL.py +++ b/cognee/modules/users/models/ACL.py @@ -5,13 +5,14 @@ from cognee.infrastructure.databases.relational import Base from .ACLResources import ACLResources + class ACL(Base): __tablename__ = "acls" - id = Column(UUID, primary_key = True, default = uuid4) + id = Column(UUID, primary_key=True, default=uuid4) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime(timezone = True), onupdate = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) principal_id = Column(UUID, ForeignKey("principals.id")) permission_id = Column(UUID, ForeignKey("permissions.id")) @@ -20,6 +21,6 @@ class ACL(Base): permission = relationship("Permission") resources: Mapped[list["Resource"]] = relationship( "Resource", - secondary = ACLResources.__tablename__, - back_populates = "acls", + secondary=ACLResources.__tablename__, + back_populates="acls", ) diff --git a/cognee/modules/users/models/ACLResources.py b/cognee/modules/users/models/ACLResources.py index 464fed2e8..020ced6c0 100644 --- a/cognee/modules/users/models/ACLResources.py +++ b/cognee/modules/users/models/ACLResources.py @@ -2,10 +2,11 @@ from sqlalchemy import Column, ForeignKey, DateTime, UUID from cognee.infrastructure.databases.relational import Base + class ACLResources(Base): __tablename__ = "acl_resources" - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) - acl_id = Column(UUID, ForeignKey("acls.id"), primary_key = True) - resource_id = Column(UUID, ForeignKey("resources.id"), primary_key = True) + acl_id = Column(UUID, ForeignKey("acls.id"), primary_key=True) + resource_id = Column(UUID, ForeignKey("resources.id"), primary_key=True) diff --git a/cognee/modules/users/models/Group.py b/cognee/modules/users/models/Group.py index 793decb35..8d6119327 100644 --- a/cognee/modules/users/models/Group.py +++ b/cognee/modules/users/models/Group.py @@ -3,17 +3,18 @@ from .Principal import Principal from .UserGroup import UserGroup + class Group(Principal): __tablename__ = "groups" - id = Column(UUID, ForeignKey("principals.id"), primary_key = True) + id = Column(UUID, ForeignKey("principals.id"), primary_key=True) - name = Column(String, unique = True, nullable = False, index = True) + name = Column(String, unique=True, nullable=False, index=True) users: Mapped[list["User"]] = relationship( "User", - secondary = UserGroup.__tablename__, - back_populates = "groups", + secondary=UserGroup.__tablename__, + back_populates="groups", ) __mapper_args__ = { diff --git a/cognee/modules/users/models/GroupPermission.py b/cognee/modules/users/models/GroupPermission.py index eaf3630b4..fd2ec4b76 100644 --- a/cognee/modules/users/models/GroupPermission.py +++ b/cognee/modules/users/models/GroupPermission.py @@ -2,10 +2,11 @@ from sqlalchemy import Column, ForeignKey, DateTime, UUID from cognee.infrastructure.databases.relational import Base + class GroupPermission(Base): __tablename__ = "group_permissions" - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) - group_id = Column(UUID, ForeignKey("groups.id"), primary_key = True) - permission_id = Column(UUID, ForeignKey("permissions.id"), primary_key = True) + group_id = Column(UUID, ForeignKey("groups.id"), primary_key=True) + permission_id = Column(UUID, ForeignKey("permissions.id"), primary_key=True) diff --git a/cognee/modules/users/models/Permission.py b/cognee/modules/users/models/Permission.py index 3b1709371..46406f0b5 100644 --- a/cognee/modules/users/models/Permission.py +++ b/cognee/modules/users/models/Permission.py @@ -1,17 +1,19 @@ from uuid import uuid4 from datetime import datetime, timezone + # from sqlalchemy.orm import relationship from sqlalchemy import Column, DateTime, String, UUID from cognee.infrastructure.databases.relational import Base + class Permission(Base): __tablename__ = "permissions" - id = Column(UUID, primary_key = True, index = True, default = uuid4) + id = Column(UUID, primary_key=True, index=True, default=uuid4) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime(timezone = True), onupdate = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) - name = Column(String, unique = True, nullable = False, index = True) + name = Column(String, unique=True, nullable=False, index=True) # acls = relationship("ACL", back_populates = "permission") diff --git a/cognee/modules/users/models/Principal.py b/cognee/modules/users/models/Principal.py index dc6e51302..a595a636a 100644 --- a/cognee/modules/users/models/Principal.py +++ b/cognee/modules/users/models/Principal.py @@ -3,15 +3,16 @@ from sqlalchemy import Column, String, DateTime, UUID from cognee.infrastructure.databases.relational import Base + class Principal(Base): __tablename__ = "principals" - id = Column(UUID, primary_key = True, index = True, default = uuid4) + id = Column(UUID, primary_key=True, index=True, default=uuid4) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime(timezone = True), onupdate = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) - type = Column(String, nullable = False) + type = Column(String, nullable=False) __mapper_args__ = { "polymorphic_identity": "principal", diff --git a/cognee/modules/users/models/Resource.py b/cognee/modules/users/models/Resource.py index 563f96272..c7e3a148b 100644 --- a/cognee/modules/users/models/Resource.py +++ b/cognee/modules/users/models/Resource.py @@ -5,14 +5,15 @@ from cognee.infrastructure.databases.relational import Base from .ACLResources import ACLResources + class Resource(Base): __tablename__ = "resources" - id = Column(UUID, primary_key = True, default = uuid4) + id = Column(UUID, primary_key=True, default=uuid4) - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime(timezone = True), onupdate = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc)) - resource_id = Column(UUID, nullable = False) + resource_id = Column(UUID, nullable=False) - acls = relationship("ACL", secondary = ACLResources.__tablename__, back_populates = "resources") + acls = relationship("ACL", secondary=ACLResources.__tablename__, back_populates="resources") diff --git a/cognee/modules/users/models/User.py b/cognee/modules/users/models/User.py index 3536ac948..0cbe75037 100644 --- a/cognee/modules/users/models/User.py +++ b/cognee/modules/users/models/User.py @@ -5,15 +5,17 @@ from .Principal import Principal from .UserGroup import UserGroup from .Group import Group +from fastapi_users import schemas + class User(SQLAlchemyBaseUserTableUUID, Principal): __tablename__ = "users" - id = Column(UUID, ForeignKey("principals.id"), primary_key = True) + id = Column(UUID, ForeignKey("principals.id"), primary_key=True) groups: Mapped[list["Group"]] = relationship( - secondary = UserGroup.__tablename__, - back_populates = "users", + secondary=UserGroup.__tablename__, + back_populates="users", ) __mapper_args__ = { @@ -22,13 +24,15 @@ class User(SQLAlchemyBaseUserTableUUID, Principal): # Keep these schemas in sync with User model -from fastapi_users import schemas + class UserRead(schemas.BaseUser[uuid_UUID]): pass + class UserCreate(schemas.BaseUserCreate): pass + class UserUpdate(schemas.BaseUserUpdate): pass diff --git a/cognee/modules/users/models/UserGroup.py b/cognee/modules/users/models/UserGroup.py index 5a85c9d3c..a4ce8f13d 100644 --- a/cognee/modules/users/models/UserGroup.py +++ b/cognee/modules/users/models/UserGroup.py @@ -2,10 +2,11 @@ from sqlalchemy import Column, ForeignKey, DateTime, UUID from cognee.infrastructure.databases.relational import Base + class UserGroup(Base): __tablename__ = "user_groups" - created_at = Column(DateTime(timezone = True), default = lambda: datetime.now(timezone.utc)) + created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) - user_id = Column(UUID, ForeignKey("users.id"), primary_key = True) - group_id = Column(UUID, ForeignKey("groups.id"), primary_key = True) + user_id = Column(UUID, ForeignKey("users.id"), primary_key=True) + group_id = Column(UUID, ForeignKey("groups.id"), primary_key=True) diff --git a/cognee/modules/users/permissions/methods/check_permission_on_documents.py b/cognee/modules/users/permissions/methods/check_permission_on_documents.py index f9c5a2258..0a292bfa0 100644 --- a/cognee/modules/users/permissions/methods/check_permission_on_documents.py +++ b/cognee/modules/users/permissions/methods/check_permission_on_documents.py @@ -1,4 +1,4 @@ -import logging +import logging from uuid import UUID from sqlalchemy import select from sqlalchemy.orm import joinedload @@ -23,11 +23,13 @@ async def check_permission_on_documents(user: User, permission_type: str, docume .join(ACL.permission) .options(joinedload(ACL.resources)) .where(ACL.principal_id.in_([user.id, *user_group_ids])) - .where(ACL.permission.has(name = permission_type)) + .where(ACL.permission.has(name=permission_type)) ) acls = result.unique().scalars().all() resource_ids = [resource.resource_id for acl in acls for resource in acl.resources] has_permissions = all(document_id in resource_ids for document_id in document_ids) if not has_permissions: - raise PermissionDeniedError(message=f"User {user.email} does not have {permission_type} permission on documents") + raise PermissionDeniedError( + message=f"User {user.email} does not have {permission_type} permission on documents" + ) diff --git a/cognee/modules/users/permissions/methods/get_document_ids_for_user.py b/cognee/modules/users/permissions/methods/get_document_ids_for_user.py index d439fb4f5..0d641960c 100644 --- a/cognee/modules/users/permissions/methods/get_document_ids_for_user.py +++ b/cognee/modules/users/permissions/methods/get_document_ids_for_user.py @@ -10,38 +10,42 @@ async def get_document_ids_for_user(user_id: UUID, datasets: list[str] = None) - async with db_engine.get_async_session() as session: async with session.begin(): - document_ids = (await session.scalars( - select(Resource.resource_id) - .join(ACL.resources) - .join(ACL.permission) - .where( - ACL.principal_id == user_id, - Permission.name == "read", + document_ids = ( + await session.scalars( + select(Resource.resource_id) + .join(ACL.resources) + .join(ACL.permission) + .where( + ACL.principal_id == user_id, + Permission.name == "read", + ) ) - )).all() + ).all() if datasets: documents_ids_in_dataset = set() # If datasets are specified filter out documents that aren't part of the specified datasets for dataset in datasets: # Find dataset id for dataset element - dataset_id = (await session.scalars( - select(Dataset.id) - .where( - Dataset.name == dataset, - Dataset.owner_id == user_id, + dataset_id = ( + await session.scalars( + select(Dataset.id).where( + Dataset.name == dataset, + Dataset.owner_id == user_id, + ) ) - )).one_or_none() + ).one_or_none() # Check which documents are connected to this dataset for document_id in document_ids: - data_id = (await session.scalars( - select(DatasetData.data_id) - .where( - DatasetData.dataset_id == dataset_id, - DatasetData.data_id == document_id, + data_id = ( + await session.scalars( + select(DatasetData.data_id).where( + DatasetData.dataset_id == dataset_id, + DatasetData.data_id == document_id, + ) ) - )).one_or_none() + ).one_or_none() # If document is related to dataset added it to return value if data_id: diff --git a/cognee/modules/users/permissions/methods/give_permission_on_document.py b/cognee/modules/users/permissions/methods/give_permission_on_document.py index b724be631..1e13463f8 100644 --- a/cognee/modules/users/permissions/methods/give_permission_on_document.py +++ b/cognee/modules/users/permissions/methods/give_permission_on_document.py @@ -2,6 +2,7 @@ from cognee.infrastructure.databases.relational import get_relational_engine from ...models import User, ACL, Resource, Permission + async def give_permission_on_document( user: User, document_id: str, @@ -9,15 +10,19 @@ async def give_permission_on_document( ): db_engine = get_relational_engine() - document_resource = Resource(resource_id = document_id) + document_resource = Resource(resource_id=document_id) async with db_engine.get_async_session() as session: - permission = (await session.execute(select(Permission).filter(Permission.name == permission_name))).scalars().first() + permission = ( + (await session.execute(select(Permission).filter(Permission.name == permission_name))) + .scalars() + .first() + ) if permission is None: - permission = Permission(name = permission_name) + permission = Permission(name=permission_name) - acl = ACL(principal_id = user.id) + acl = ACL(principal_id=user.id) acl.permission = permission acl.resources.append(document_resource) diff --git a/cognee/root_dir.py b/cognee/root_dir.py index 0f96b8065..2e21d5ce3 100644 --- a/cognee/root_dir.py +++ b/cognee/root_dir.py @@ -2,6 +2,7 @@ ROOT_DIR = Path(__file__).resolve().parent + def get_absolute_path(path_from_root: str) -> str: absolute_path = ROOT_DIR / path_from_root return str(absolute_path.resolve()) diff --git a/cognee/shared/CodeGraphEntities.py b/cognee/shared/CodeGraphEntities.py index 27289493d..164327da0 100644 --- a/cognee/shared/CodeGraphEntities.py +++ b/cognee/shared/CodeGraphEntities.py @@ -5,10 +5,8 @@ class Repository(DataPoint): __tablename__ = "Repository" path: str - _metadata: dict = { - "index_fields": [], - "type": "Repository" - } + _metadata: dict = {"index_fields": [], "type": "Repository"} + class CodeFile(DataPoint): __tablename__ = "codefile" @@ -18,19 +16,15 @@ class CodeFile(DataPoint): depends_on: Optional[List["CodeFile"]] = None depends_directly_on: Optional[List["CodeFile"]] = None contains: Optional[List["CodePart"]] = None - _metadata: dict = { - "index_fields": [], - "type": "CodeFile" - } + _metadata: dict = {"index_fields": [], "type": "CodeFile"} + class CodePart(DataPoint): __tablename__ = "codepart" # part_of: Optional[CodeFile] = None source_code: Optional[str] = None - _metadata: dict = { - "index_fields": [], - "type": "CodePart" - } + _metadata: dict = {"index_fields": [], "type": "CodePart"} + class SourceCodeChunk(DataPoint): __tablename__ = "sourcecodechunk" @@ -38,11 +32,9 @@ class SourceCodeChunk(DataPoint): source_code: Optional[str] = None previous_chunk: Optional["SourceCodeChunk"] = None - _metadata: dict = { - "index_fields": ["source_code"], - "type": "SourceCodeChunk" - } + _metadata: dict = {"index_fields": ["source_code"], "type": "SourceCodeChunk"} + CodeFile.model_rebuild() CodePart.model_rebuild() -SourceCodeChunk.model_rebuild() \ No newline at end of file +SourceCodeChunk.model_rebuild() diff --git a/cognee/shared/GithubClassification.py b/cognee/shared/GithubClassification.py index 66f14ec91..2642b9e98 100644 --- a/cognee/shared/GithubClassification.py +++ b/cognee/shared/GithubClassification.py @@ -4,7 +4,6 @@ from pydantic import BaseModel - class TextSubclass(str, Enum): SOURCE_CODE = "Source code in various programming languages" SHELL_SCRIPTS = "Shell commands and scripts" @@ -12,14 +11,20 @@ class TextSubclass(str, Enum): STYLESHEETS = "Stylesheets (CSS) and configuration files (YAML, JSON, INI)" OTHER = "Other that does not fit into any of the above categories" + class ContentType(BaseModel): """Base class for content type, storing type of content as string.""" + type: str = "TEXT" + class TextContent(ContentType): """Textual content class for more specific text categories.""" + subclass: List[TextSubclass] + class CodeContentPrediction(BaseModel): """Model to predict the type of content.""" - label: TextContent \ No newline at end of file + + label: TextContent diff --git a/cognee/shared/GithubTopology.py b/cognee/shared/GithubTopology.py index 3315c72e4..50c74705a 100644 --- a/cognee/shared/GithubTopology.py +++ b/cognee/shared/GithubTopology.py @@ -1,29 +1,33 @@ - - from pydantic import BaseModel from typing import List, Optional, Dict, Any, Union + class Relationship(BaseModel): type: str attributes: Optional[Dict[str, Any]] = {} + class Document(BaseModel): name: str content: str filetype: str + class Directory(BaseModel): name: str documents: List[Document] = [] - directories: List['Directory'] = [] + directories: List["Directory"] = [] + # Allows recursive Directory Model Directory.model_rebuild() + class RepositoryProperties(BaseModel): custom_properties: Optional[Dict[str, Any]] = None location: Optional[str] = None # Simplified location reference + class RepositoryNode(BaseModel): node_id: str node_type: str # 'document' or 'directory' @@ -31,6 +35,7 @@ class RepositoryNode(BaseModel): content: Union[Document, Directory, None] = None relationships: List[Relationship] = [] + class RepositoryGraphModel(BaseModel): root: RepositoryNode default_relationships: List[Relationship] = [] diff --git a/cognee/shared/SourceCodeGraph.py b/cognee/shared/SourceCodeGraph.py index 3de72c5fd..6c8cea3e9 100644 --- a/cognee/shared/SourceCodeGraph.py +++ b/cognee/shared/SourceCodeGraph.py @@ -11,20 +11,16 @@ class Variable(DataPoint): default_value: Optional[str] = None data_type: str - _metadata = { - "index_fields": ["name"], - "type": "Variable" - } + _metadata = {"index_fields": ["name"], "type": "Variable"} + class Operator(DataPoint): id: str name: str description: str return_type: str - _metadata = { - "index_fields": ["name"], - "type": "Operator" - } + _metadata = {"index_fields": ["name"], "type": "Operator"} + class Class(DataPoint): id: str @@ -34,10 +30,8 @@ class Class(DataPoint): extended_from_class: Optional["Class"] = None has_methods: List["Function"] - _metadata = { - "index_fields": ["name"], - "type": "Class" - } + _metadata = {"index_fields": ["name"], "type": "Class"} + class ClassInstance(DataPoint): id: str @@ -47,10 +41,8 @@ class ClassInstance(DataPoint): instantiated_by: Union["Function"] instantiation_arguments: List[Variable] - _metadata = { - "index_fields": ["name"], - "type": "ClassInstance" - } + _metadata = {"index_fields": ["name"], "type": "ClassInstance"} + class Function(DataPoint): id: str @@ -60,20 +52,16 @@ class Function(DataPoint): return_type: str is_static: Optional[bool] = False - _metadata = { - "index_fields": ["name"], - "type": "Function" - } + _metadata = {"index_fields": ["name"], "type": "Function"} + class FunctionCall(DataPoint): id: str called_by: Union[Function, Literal["main"]] function_called: Function function_arguments: List[Any] - _metadata = { - "index_fields": [], - "type": "FunctionCall" - } + _metadata = {"index_fields": [], "type": "FunctionCall"} + class Expression(DataPoint): id: str @@ -81,32 +69,30 @@ class Expression(DataPoint): description: str expression: str members: List[Union[Variable, Function, Operator, "Expression"]] - _metadata = { - "index_fields": ["name"], - "type": "Expression" - } + _metadata = {"index_fields": ["name"], "type": "Expression"} + class SourceCodeGraph(DataPoint): id: str name: str description: str language: str - nodes: List[Union[ - Class, - ClassInstance, - Function, - FunctionCall, - Variable, - Operator, - Expression, - ]] - _metadata = { - "index_fields": ["name"], - "type": "SourceCodeGraph" - } + nodes: List[ + Union[ + Class, + ClassInstance, + Function, + FunctionCall, + Variable, + Operator, + Expression, + ] + ] + _metadata = {"index_fields": ["name"], "type": "SourceCodeGraph"} + Class.model_rebuild() ClassInstance.model_rebuild() Expression.model_rebuild() FunctionCall.model_rebuild() -SourceCodeGraph.model_rebuild() \ No newline at end of file +SourceCodeGraph.model_rebuild() diff --git a/cognee/shared/data_models.py b/cognee/shared/data_models.py index 2a8bc8c91..d23d2841c 100644 --- a/cognee/shared/data_models.py +++ b/cognee/shared/data_models.py @@ -8,37 +8,51 @@ class Node(BaseModel): """Node in a knowledge graph.""" + id: str name: str type: str description: str - properties: Optional[Dict[str, Any]] = Field(None, description = "A dictionary of properties associated with the node.") + properties: Optional[Dict[str, Any]] = Field( + None, description="A dictionary of properties associated with the node." + ) + class Edge(BaseModel): """Edge in a knowledge graph.""" + source_node_id: str target_node_id: str relationship_name: str - properties: Optional[Dict[str, Any]] = Field(None, description = "A dictionary of properties associated with the edge.") + properties: Optional[Dict[str, Any]] = Field( + None, description="A dictionary of properties associated with the edge." + ) + class KnowledgeGraph(BaseModel): """Knowledge graph.""" + nodes: List[Node] = Field(..., default_factory=list) edges: List[Edge] = Field(..., default_factory=list) + class GraphQLQuery(BaseModel): """GraphQL query.""" + query: str + class Answer(BaseModel): """Answer.""" + answer: str + class ChunkStrategy(Enum): EXACT = "exact" PARAGRAPH = "paragraph" SENTENCE = "sentence" - CODE = "code" + CODE = "code" LANGCHAIN_CHARACTER = "langchain_character" @@ -47,11 +61,14 @@ class ChunkEngine(Enum): DEFAULT_ENGINE = "default" HAYSTACK_ENGINE = "haystack" + class MemorySummary(BaseModel): - """ Memory summary. """ + """Memory summary.""" + nodes: List[Node] = Field(..., default_factory=list) edges: List[Edge] = Field(..., default_factory=list) + class TextSubclass(str, Enum): ARTICLES = "Articles, essays, and reports" BOOKS = "Books and manuscripts" @@ -100,6 +117,7 @@ class TextSubclass(str, Enum): LEGAL_AND_REGULATORY_DOCUMENTS = "Legal and Regulatory Documents" OTHER_TEXT = "Other types of text data" + class AudioSubclass(str, Enum): MUSIC_TRACKS = "Music tracks and albums" PODCASTS = "Podcasts and radio broadcasts" @@ -108,6 +126,7 @@ class AudioSubclass(str, Enum): SOUND_EFFECTS = "Sound effects and ambient sounds" OTHER_AUDIO = "Other types of audio recordings" + class ImageSubclass(str, Enum): PHOTOGRAPHS = "Photographs and digital images" ILLUSTRATIONS = "Illustrations, diagrams, and charts" @@ -116,6 +135,7 @@ class ImageSubclass(str, Enum): SCREENSHOTS = "Screenshots and graphical user interfaces" OTHER_IMAGES = "Other types of images" + class VideoSubclass(str, Enum): MOVIES = "Movies and short films" DOCUMENTARIES = "Documentaries and educational videos" @@ -124,6 +144,7 @@ class VideoSubclass(str, Enum): LIVE_EVENTS = "Live event recordings and sports broadcasts" OTHER_VIDEOS = "Other types of video content" + class MultimediaSubclass(str, Enum): WEB_CONTENT = "Interactive web content and games" VR_EXPERIENCES = "Virtual reality (VR) and augmented reality (AR) experiences" @@ -132,6 +153,7 @@ class MultimediaSubclass(str, Enum): DIGITAL_EXHIBITIONS = "Digital exhibitions and virtual tours" OTHER_MULTIMEDIA = "Other types of multimedia content" + class Model3DSubclass(str, Enum): ARCHITECTURAL_RENDERINGS = "Architectural renderings and building plans" PRODUCT_MODELS = "Product design models and prototypes" @@ -140,6 +162,7 @@ class Model3DSubclass(str, Enum): VR_OBJECTS = "Virtual objects for AR/VR applications" OTHER_3D_MODELS = "Other types of 3D models" + class ProceduralSubclass(str, Enum): TUTORIALS_GUIDES = "Tutorials and step-by-step guides" WORKFLOW_DESCRIPTIONS = "Workflow and process descriptions" @@ -147,40 +170,51 @@ class ProceduralSubclass(str, Enum): RECIPES = "Recipes and crafting instructions" OTHER_PROCEDURAL = "Other types of procedural content" + class ContentType(BaseModel): """Base class for different types of content.""" + type: str + class TextContent(ContentType): type: str = "TEXTUAL_DOCUMENTS_USED_FOR_GENERAL_PURPOSES" subclass: List[TextSubclass] + class AudioContent(ContentType): type: str = "AUDIO_DOCUMENTS_USED_FOR_GENERAL_PURPOSES" subclass: List[AudioSubclass] + class ImageContent(ContentType): type: str = "IMAGE_DOCUMENTS_USED_FOR_GENERAL_PURPOSES" subclass: List[ImageSubclass] + class VideoContent(ContentType): type: str = "VIDEO_DOCUMENTS_USED_FOR_GENERAL_PURPOSES" subclass: List[VideoSubclass] + class MultimediaContent(ContentType): type: str = "MULTIMEDIA_DOCUMENTS_USED_FOR_GENERAL_PURPOSES" subclass: List[MultimediaSubclass] + class Model3DContent(ContentType): type: str = "3D_MODEL_DOCUMENTS_USED_FOR_GENERAL_PURPOSES" subclass: List[Model3DSubclass] + class ProceduralContent(ContentType): type: str = "PROCEDURAL_DOCUMENTS_USED_FOR_GENERAL_PURPOSES" subclass: List[ProceduralSubclass] + class DefaultContentPrediction(BaseModel): """Class for a single class label prediction.""" + label: Union[ TextContent, AudioContent, @@ -191,11 +225,14 @@ class DefaultContentPrediction(BaseModel): ProceduralContent, ] + class SummarizedContent(BaseModel): """Class for a single class label summary and description.""" + summary: str description: str + class SummarizedFunction(BaseModel): name: str description: str @@ -203,12 +240,14 @@ class SummarizedFunction(BaseModel): outputs: Optional[List[str]] = None decorators: Optional[List[str]] = None + class SummarizedClass(BaseModel): name: str description: str methods: Optional[List[SummarizedFunction]] = None decorators: Optional[List[str]] = None + class SummarizedCode(BaseModel): high_level_summary: str key_features: List[str] @@ -232,50 +271,58 @@ class Relationship(BaseModel): target: Optional[str] = None properties: Optional[Dict[str, Any]] = None + class DocumentType(BaseModel): type_id: str description: str - default_relationship: Relationship = Relationship(type = "is_type") + default_relationship: Relationship = Relationship(type="is_type") + class Category(BaseModel): category_id: str name: str - default_relationship: Relationship = Relationship(type = "categorized_as") + default_relationship: Relationship = Relationship(type="categorized_as") + class Document(BaseModel): id: str type: str title: str + class UserLocation(BaseModel): location_id: str description: str - default_relationship: Relationship = Relationship(type = "located_in") + default_relationship: Relationship = Relationship(type="located_in") + class UserProperties(BaseModel): custom_properties: Optional[Dict[str, Any]] = None location: Optional[UserLocation] = None + class DefaultGraphModel(BaseModel): node_id: str user_properties: UserProperties = UserProperties() documents: List[Document] = [] default_fields: Optional[Dict[str, Any]] = {} - default_relationship: Relationship = Relationship(type = "has_properties") + default_relationship: Relationship = Relationship(type="has_properties") class ChunkSummary(BaseModel): text: str chunk_id: str + class ChunkSummaries(BaseModel): - """ Relevant summary and chunk id """ + """Relevant summary and chunk id""" + summaries: List[ChunkSummary] class MonitoringTool(str, Enum): - """ Monitoring tools """ + """Monitoring tools""" + LANGFUSE = "langfuse" LLMLITE = "llmlite" LANGSMITH = "langsmith" - diff --git a/cognee/shared/encode_uuid.py b/cognee/shared/encode_uuid.py index 21fda1a91..230fd7c27 100644 --- a/cognee/shared/encode_uuid.py +++ b/cognee/shared/encode_uuid.py @@ -1,11 +1,12 @@ from uuid import UUID + def encode_uuid(uuid: UUID) -> str: uuid_int = uuid.int base = 52 - charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - encoded = '' + encoded = "" while len(encoded) < 36: uuid_int, remainder = divmod(uuid_int, base) uuid_int = uuid_int * 8 diff --git a/cognee/shared/exceptions/__init__.py b/cognee/shared/exceptions/__init__.py index 9b86cccab..5e2ae6875 100644 --- a/cognee/shared/exceptions/__init__.py +++ b/cognee/shared/exceptions/__init__.py @@ -6,4 +6,4 @@ from .exceptions import ( IngestionError, -) \ No newline at end of file +) diff --git a/cognee/shared/exceptions/exceptions.py b/cognee/shared/exceptions/exceptions.py index 101711398..4b4164995 100644 --- a/cognee/shared/exceptions/exceptions.py +++ b/cognee/shared/exceptions/exceptions.py @@ -1,11 +1,12 @@ from cognee.exceptions import CogneeApiError from fastapi import status + class IngestionError(CogneeApiError): def __init__( - self, - message: str = "Failed to load data.", - name: str = "IngestionError", - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + self, + message: str = "Failed to load data.", + name: str = "IngestionError", + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, ): - super().__init__(message, name, status_code) \ No newline at end of file + super().__init__(message, name, status_code) diff --git a/cognee/shared/utils.py b/cognee/shared/utils.py index 4f0b1bc3b..e57decde1 100644 --- a/cognee/shared/utils.py +++ b/cognee/shared/utils.py @@ -1,4 +1,5 @@ -""" This module contains utility functions for the cognee. """ +"""This module contains utility functions for the cognee.""" + import os from typing import BinaryIO, Union @@ -7,11 +8,13 @@ from datetime import datetime, timezone import graphistry import networkx as nx -import numpy as np import pandas as pd import matplotlib.pyplot as plt import tiktoken import nltk +import base64 + + import logging import sys @@ -26,6 +29,7 @@ # Analytics Proxy Url, currently hosted by Vercel proxy_url = "https://test.prometh.ai" + def get_anonymous_id(): """Creates or reads a anonymous user id""" home_dir = str(pathlib.Path(pathlib.Path(__file__).parent.parent.parent.resolve())) @@ -42,6 +46,7 @@ def get_anonymous_id(): anonymous_id = f.read() return anonymous_id + def send_telemetry(event_name: str, user_id, additional_properties: dict = {}): if os.getenv("TELEMETRY_DISABLED"): return @@ -60,7 +65,7 @@ def send_telemetry(event_name: str, user_id, additional_properties: dict = {}): "properties": { "time": current_time.strftime("%m/%d/%Y"), "user_id": str(user_id), - **additional_properties + **additional_properties, }, } @@ -69,6 +74,7 @@ def send_telemetry(event_name: str, user_id, additional_properties: dict = {}): if response.status_code != 200: print(f"Error sending telemetry through proxy: {response.status_code}") + def num_tokens_from_string(string: str, encoding_name: str) -> int: """Returns the number of tokens in a text string.""" @@ -77,12 +83,13 @@ def num_tokens_from_string(string: str, encoding_name: str) -> int: num_tokens = len(encoding.encode(string)) return num_tokens + def get_file_content_hash(file_obj: Union[str, BinaryIO]) -> str: h = hashlib.md5() try: if isinstance(file_obj, str): - with open(file_obj, 'rb') as file: + with open(file_obj, "rb") as file: while True: # Reading is buffered, so we can read smaller chunks. chunk = file.read(h.block_size) @@ -101,6 +108,7 @@ def get_file_content_hash(file_obj: Union[str, BinaryIO]) -> str: except IOError as e: raise IngestionError(message=f"Failed to load data from {file}: {e}") + def trim_text_to_max_tokens(text: str, max_tokens: int, encoding_name: str) -> str: """ Trims the text so that the number of tokens does not exceed max_tokens. @@ -132,22 +140,30 @@ def trim_text_to_max_tokens(text: str, max_tokens: int, encoding_name: str) -> s def generate_color_palette(unique_layers): colormap = plt.cm.get_cmap("viridis", len(unique_layers)) colors = [colormap(i) for i in range(len(unique_layers))] - hex_colors = ["#%02x%02x%02x" % (int(rgb[0]*255), int(rgb[1]*255), int(rgb[2]*255)) for rgb in colors] + hex_colors = [ + "#%02x%02x%02x" % (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255)) + for rgb in colors + ] return dict(zip(unique_layers, hex_colors)) async def register_graphistry(): config = get_base_config() - graphistry.register(api = 3, username = config.graphistry_username, password = config.graphistry_password) + graphistry.register( + api=3, username=config.graphistry_username, password=config.graphistry_password + ) def prepare_edges(graph, source, target, edge_key): - edge_list = [{ - source: str(edge[0]), - target: str(edge[1]), - edge_key: str(edge[2]), - } for edge in graph.edges(keys = True, data = True)] + edge_list = [ + { + source: str(edge[0]), + target: str(edge[1]), + edge_key: str(edge[2]), + } + for edge in graph.edges(keys=True, data=True) + ] return pd.DataFrame(edge_list) @@ -169,7 +185,9 @@ def prepare_nodes(graph, include_size=False): default_size = 10 # Default node size larger_size = 20 # Size for nodes with specific keywords in their ID keywords = ["DOCUMENT", "User"] - node_size = larger_size if any(keyword in str(node) for keyword in keywords) else default_size + node_size = ( + larger_size if any(keyword in str(node) for keyword in keywords) else default_size + ) node_data["size"] = node_size nodes_data.append(node_data) @@ -177,7 +195,9 @@ def prepare_nodes(graph, include_size=False): return pd.DataFrame(nodes_data) -async def render_graph(graph, include_nodes=False, include_color=False, include_size=False, include_labels=False): +async def render_graph( + graph, include_nodes=False, include_color=False, include_size=False, include_labels=False +): await register_graphistry() if not isinstance(graph, nx.MultiDiGraph): @@ -193,15 +213,14 @@ async def render_graph(graph, include_nodes=False, include_color=False, include_ edges = prepare_edges(graph, "source_node", "target_node", "relationship_name") plotter = graphistry.edges(edges, "source_node", "target_node") - plotter = plotter.bind(edge_label = "relationship_name") + plotter = plotter.bind(edge_label="relationship_name") if include_nodes: - nodes = prepare_nodes(graph, include_size = include_size) + nodes = prepare_nodes(graph, include_size=include_size) plotter = plotter.nodes(nodes, "id") if include_size: - plotter = plotter.bind(point_size = "size") - + plotter = plotter.bind(point_size="size") if include_color: pass @@ -210,10 +229,8 @@ async def render_graph(graph, include_nodes=False, include_color=False, include_ # plotter = plotter.encode_point_color("layer_description", categorical_mapping=color_palette, # default_mapping="silver") - if include_labels: - plotter = plotter.bind(point_label = "name") - + plotter = plotter.bind(point_label="name") # Visualization url = plotter.plot(render=False, as_files=True, memoize=False) @@ -221,14 +238,15 @@ async def render_graph(graph, include_nodes=False, include_color=False, include_ return url -def sanitize_df(df): - """Replace NaNs and infinities in a DataFrame with None, making it JSON compliant.""" - return df.replace([np.inf, -np.inf, np.nan], None) +# def sanitize_df(df): +# """Replace NaNs and infinities in a DataFrame with None, making it JSON compliant.""" +# return df.replace([np.inf, -np.inf, np.nan], None) def get_entities(tagged_tokens): nltk.download("maxent_ne_chunker", quiet=True) from nltk.chunk import ne_chunk + return ne_chunk(tagged_tokens) @@ -252,41 +270,205 @@ def extract_pos_tags(sentence): return pos_tags -def extract_named_entities(sentence): - """Extract Named Entities from a sentence.""" - # Tokenize the sentence into words - tagged_tokens = extract_pos_tags(sentence) +logging.basicConfig(level=logging.INFO) + + +async def convert_to_serializable_graph(G): + """ + Convert a graph into a serializable format with stringified node and edge attributes. + """ - # Perform Named Entity Recognition (NER) on the tagged tokens - entities = get_entities(tagged_tokens) + (nodes, edges) = G - return entities + networkx_graph = nx.MultiDiGraph() + networkx_graph.add_nodes_from(nodes) + networkx_graph.add_edges_from(edges) + # Create a new graph to store the serializable version + new_G = nx.MultiDiGraph() -def extract_sentiment_vader(text): + # Serialize nodes + for node, data in networkx_graph.nodes(data=True): + serializable_data = {k: str(v) for k, v in data.items()} + new_G.add_node(str(node), **serializable_data) + + # Serialize edges + for u, v, data in networkx_graph.edges(data=True): + serializable_data = {k: str(v) for k, v in data.items()} + new_G.add_edge(str(u), str(v), **serializable_data) + + return new_G + + +def generate_layout_positions(G, layout_func, layout_scale): + """ + Generate layout positions for the graph using the specified layout function. """ - Analyzes the sentiment of a given text using the VADER Sentiment Intensity Analyzer. + positions = layout_func(G) + return {str(node): (x * layout_scale, y * layout_scale) for node, (x, y) in positions.items()} - Parameters: - text (str): The text to analyze. - Returns: - dict: A dictionary containing the polarity scores for the text. +def assign_node_colors(G, node_attribute, palette): """ - from nltk.sentiment import SentimentIntensityAnalyzer + Assign colors to nodes based on a specified attribute and a given palette. + """ + unique_attrs = set(G.nodes[node].get(node_attribute, "Unknown") for node in G.nodes) + color_map = {attr: palette[i % len(palette)] for i, attr in enumerate(unique_attrs)} + return [color_map[G.nodes[node].get(node_attribute, "Unknown")] for node in G.nodes], color_map + + +def embed_logo(p, layout_scale, logo_alpha, position): + """ + Embed a logo into the graph visualization as a watermark. + """ + + # svg_logo = """ + # + # + # + # + # + # + # """ + # Convert the SVG to a ReportLab Drawing + + logo_url = "data:image/png;base64,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" + + # Finally, add the PNG image to your Bokeh plot as an image_url + p.image_url( + url=[logo_url], + x=-layout_scale * 0.5, + y=layout_scale * 0.5, + w=layout_scale, + h=layout_scale, + anchor=position, + global_alpha=logo_alpha, + ) + + +def style_and_render_graph(p, G, layout_positions, node_attribute, node_colors, centrality): + """ + Apply styling and render the graph into the plot. + """ + from bokeh.plotting import figure, from_networkx + from bokeh.models import Circle, MultiLine, HoverTool, ColumnDataSource, Range1d + from bokeh.plotting import output_file, show + + from bokeh.embed import file_html + from bokeh.resources import CDN + + graph_renderer = from_networkx(G, layout_positions) + node_radii = [0.02 + 0.1 * centrality[node] for node in G.nodes()] + graph_renderer.node_renderer.data_source.data["radius"] = node_radii + graph_renderer.node_renderer.data_source.data["fill_color"] = node_colors + graph_renderer.node_renderer.glyph = Circle( + radius="radius", + fill_color="fill_color", + fill_alpha=0.9, + line_color="#000000", + line_width=1.5, + ) + graph_renderer.edge_renderer.glyph = MultiLine( + line_color="#000000", + line_alpha=0.3, + line_width=1.5, + ) + p.renderers.append(graph_renderer) + return graph_renderer + + +async def create_cognee_style_network_with_logo( + G, + output_filename="cognee_network_with_logo.html", + title="Cognee-Style Network", + label="group", + layout_func=nx.spring_layout, + layout_scale=3.0, + logo_alpha=0.1, + bokeh_object=False, +): + """ + Create a Cognee-inspired network visualization with an embedded logo. + """ + from bokeh.plotting import figure, from_networkx + from bokeh.models import Circle, MultiLine, HoverTool, ColumnDataSource, Range1d + from bokeh.plotting import output_file, show + + from bokeh.embed import file_html + from bokeh.resources import CDN + + logging.info("Converting graph to serializable format...") + G = await convert_to_serializable_graph(G) + + logging.info("Generating layout positions...") + layout_positions = generate_layout_positions(G, layout_func, layout_scale) + + logging.info("Assigning node colors...") + palette = ["#6510F4", "#0DFF00", "#FFFFFF"] + node_colors, color_map = assign_node_colors(G, label, palette) + + logging.info("Calculating centrality...") + centrality = nx.degree_centrality(G) + + logging.info("Preparing Bokeh output...") + output_file(output_filename) + p = figure( + title=title, + tools="pan,wheel_zoom,save,reset,hover", + active_scroll="wheel_zoom", + width=1200, + height=900, + background_fill_color="#F4F4F4", + x_range=Range1d(-layout_scale, layout_scale), + y_range=Range1d(-layout_scale, layout_scale), + ) + p.toolbar.logo = None + p.axis.visible = False + p.grid.visible = False + + logging.info("Embedding logo into visualization...") + embed_logo(p, layout_scale, logo_alpha, "bottom_right") + embed_logo(p, layout_scale, logo_alpha, "top_left") + + logging.info("Styling and rendering graph...") + style_and_render_graph(p, G, layout_positions, label, node_colors, centrality) + + logging.info("Adding hover tool...") + hover_tool = HoverTool( + tooltips=[ + ("Node", "@index"), + (label.capitalize(), f"@{label}"), + ("Centrality", "@radius{0.00}"), + ], + ) + p.add_tools(hover_tool) + + logging.info(f"Saving visualization to {output_filename}...") + html_content = file_html(p, CDN, title) + with open(output_filename, "w") as f: + f.write(html_content) - nltk.download("vader_lexicon", quiet=True) + logging.info("Visualization complete.") - # Initialize the VADER Sentiment Intensity Analyzer - sia = SentimentIntensityAnalyzer() + if bokeh_object: + return p + return html_content - # Obtain the polarity scores for the text - polarity_scores = sia.polarity_scores(text) - return polarity_scores +def graph_to_tuple(graph): + """ + Converts a networkx graph to a tuple of (nodes, edges). + + :param graph: A networkx graph. + :return: A tuple (nodes, edges). + """ + nodes = list(graph.nodes(data=True)) # Get nodes with attributes + edges = list(graph.edges(data=True)) # Get edges with attributes + return (nodes, edges) + def setup_logging(log_level=logging.INFO): - """ This method sets up the logging configuration. """ + """This method sets up the logging configuration.""" formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s\n") stream_handler = logging.StreamHandler(sys.stdout) stream_handler.setFormatter(formatter) @@ -298,7 +480,66 @@ def setup_logging(log_level=logging.INFO): ) +# ---------------- Example Usage ---------------- if __name__ == "__main__": - sample_text = "I love sunny days, but I hate the rain." - sentiment_scores = extract_sentiment_vader(sample_text) - print("Sentiment analysis results:", sentiment_scores) + import networkx as nx + + # Create a sample graph + nodes = [ + (1, {"group": "A"}), + (2, {"group": "A"}), + (3, {"group": "B"}), + (4, {"group": "B"}), + (5, {"group": "C"}), + ] + edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)] + + # Create a NetworkX graph + G = nx.Graph() + G.add_nodes_from(nodes) + G.add_edges_from(edges) + + G = graph_to_tuple(G) + + import asyncio + + output_html = asyncio.run( + create_cognee_style_network_with_logo( + G, + output_filename="example_network.html", + title="Example Cognee Network", + node_attribute="group", # Attribute to use for coloring nodes + layout_func=nx.spring_layout, # Layout function + layout_scale=3.0, # Scale for the layout + logo_alpha=0.2, + ) + ) + + # Call the function + # output_html = await create_cognee_style_network_with_logo( + # G=G, + # output_filename="example_network.html", + # title="Example Cognee Network", + # node_attribute="group", # Attribute to use for coloring nodes + # layout_func=nx.spring_layout, # Layout function + # layout_scale=3.0, # Scale for the layout + # logo_alpha=0.2, # Transparency of the logo + # ) + + # Print the output filename + print("Network visualization saved as example_network.html") + +# # Create a random geometric graph +# G = nx.random_geometric_graph(50, 0.3) +# # Assign random group attributes for coloring +# for i, node in enumerate(G.nodes()): +# G.nodes[node]["group"] = f"Group {i % 3 + 1}" +# +# create_cognee_graph( +# G, +# output_filename="cognee_style_network_with_logo.html", +# title="Cognee-Graph Network", +# node_attribute="group", +# layout_func=nx.spring_layout, +# layout_scale=3.0, # Replace with your logo file path +# ) diff --git a/cognee/tasks/chunk_naive_llm_classifier/chunk_naive_llm_classifier.py b/cognee/tasks/chunk_naive_llm_classifier/chunk_naive_llm_classifier.py index 154dadb61..4c058de18 100644 --- a/cognee/tasks/chunk_naive_llm_classifier/chunk_naive_llm_classifier.py +++ b/cognee/tasks/chunk_naive_llm_classifier/chunk_naive_llm_classifier.py @@ -8,7 +8,9 @@ from cognee.modules.chunking.models.DocumentChunk import DocumentChunk -async def chunk_naive_llm_classifier(data_chunks: list[DocumentChunk], classification_model: Type[BaseModel]): +async def chunk_naive_llm_classifier( + data_chunks: list[DocumentChunk], classification_model: Type[BaseModel] +): if len(data_chunks) == 0: return data_chunks @@ -36,10 +38,17 @@ class Keyword(BaseModel): collection_name = "classification" if await vector_engine.has_collection(collection_name): - existing_data_points = await vector_engine.retrieve( - collection_name, - [str(classification_data) for classification_data in list(set(classification_data_points))], - ) if len(classification_data_points) > 0 else [] + existing_data_points = ( + await vector_engine.retrieve( + collection_name, + [ + str(classification_data) + for classification_data in list(set(classification_data_points)) + ], + ) + if len(classification_data_points) > 0 + else [] + ) existing_points_map = {point.id: True for point in existing_data_points} else: @@ -50,7 +59,7 @@ class Keyword(BaseModel): nodes = [] edges = [] - for (chunk_index, data_chunk) in enumerate(data_chunks): + for chunk_index, data_chunk in enumerate(data_chunks): chunk_classification = chunk_classifications[chunk_index] classification_type_label = chunk_classification.label.type classification_type_id = uuid5(NAMESPACE_OID, classification_type_label) @@ -59,36 +68,42 @@ class Keyword(BaseModel): data_points.append( DataPoint[Keyword]( id=str(classification_type_id), - payload=Keyword.model_validate({ - "uuid": str(classification_type_id), - "text": classification_type_label, - "chunk_id": str(data_chunk.chunk_id), - "document_id": str(data_chunk.document_id), - }), + payload=Keyword.model_validate( + { + "uuid": str(classification_type_id), + "text": classification_type_label, + "chunk_id": str(data_chunk.chunk_id), + "document_id": str(data_chunk.document_id), + } + ), index_fields=["text"], ) ) - nodes.append(( - str(classification_type_id), - dict( - id=str(classification_type_id), - name=classification_type_label, - type=classification_type_label, + nodes.append( + ( + str(classification_type_id), + dict( + id=str(classification_type_id), + name=classification_type_label, + type=classification_type_label, + ), ) - )) + ) existing_points_map[classification_type_id] = True - edges.append(( - str(data_chunk.chunk_id), - str(classification_type_id), - "is_media_type", - dict( - relationship_name="is_media_type", - source_node_id=str(data_chunk.chunk_id), - target_node_id=str(classification_type_id), - ), - )) + edges.append( + ( + str(data_chunk.chunk_id), + str(classification_type_id), + "is_media_type", + dict( + relationship_name="is_media_type", + source_node_id=str(data_chunk.chunk_id), + target_node_id=str(classification_type_id), + ), + ) + ) for classification_subclass in chunk_classification.label.subclass: classification_subtype_label = classification_subclass.value @@ -98,47 +113,55 @@ class Keyword(BaseModel): data_points.append( DataPoint[Keyword]( id=str(classification_subtype_id), - payload=Keyword.model_validate({ - "uuid": str(classification_subtype_id), - "text": classification_subtype_label, - "chunk_id": str(data_chunk.chunk_id), - "document_id": str(data_chunk.document_id), - }), + payload=Keyword.model_validate( + { + "uuid": str(classification_subtype_id), + "text": classification_subtype_label, + "chunk_id": str(data_chunk.chunk_id), + "document_id": str(data_chunk.document_id), + } + ), index_fields=["text"], ) ) - nodes.append(( - str(classification_subtype_id), - dict( - id=str(classification_subtype_id), - name=classification_subtype_label, - type=classification_subtype_label, + nodes.append( + ( + str(classification_subtype_id), + dict( + id=str(classification_subtype_id), + name=classification_subtype_label, + type=classification_subtype_label, + ), + ) + ) + edges.append( + ( + str(classification_subtype_id), + str(classification_type_id), + "is_subtype_of", + dict( + relationship_name="contains", + source_node_id=str(classification_type_id), + target_node_id=str(classification_subtype_id), + ), ) - )) - edges.append(( + ) + + existing_points_map[classification_subtype_id] = True + + edges.append( + ( + str(data_chunk.chunk_id), str(classification_subtype_id), - str(classification_type_id), - "is_subtype_of", + "is_classified_as", dict( - relationship_name="contains", - source_node_id=str(classification_type_id), + relationship_name="is_classified_as", + source_node_id=str(data_chunk.chunk_id), target_node_id=str(classification_subtype_id), ), - )) - - existing_points_map[classification_subtype_id] = True - - edges.append(( - str(data_chunk.chunk_id), - str(classification_subtype_id), - "is_classified_as", - dict( - relationship_name="is_classified_as", - source_node_id=str(data_chunk.chunk_id), - target_node_id=str(classification_subtype_id), - ), - )) + ) + ) if len(nodes) > 0 or len(edges) > 0: await vector_engine.create_data_points(collection_name, data_points) diff --git a/cognee/tasks/chunks/chunk_by_paragraph.py b/cognee/tasks/chunks/chunk_by_paragraph.py index 00bb5670c..5c95e97b7 100644 --- a/cognee/tasks/chunks/chunk_by_paragraph.py +++ b/cognee/tasks/chunks/chunk_by_paragraph.py @@ -2,7 +2,10 @@ from typing import Dict, Any, Iterator from .chunk_by_sentence import chunk_by_sentence -def chunk_by_paragraph(data: str, paragraph_length: int = 1024, batch_paragraphs: bool = True) -> Iterator[Dict[str, Any]]: + +def chunk_by_paragraph( + data: str, paragraph_length: int = 1024, batch_paragraphs: bool = True +) -> Iterator[Dict[str, Any]]: """ Chunks text by paragraph while preserving exact text reconstruction capability. When chunks are joined with empty string "", they reproduce the original text exactly. @@ -12,8 +15,10 @@ def chunk_by_paragraph(data: str, paragraph_length: int = 1024, batch_paragraphs chunk_index = 0 paragraph_ids = [] last_cut_type = None - - for paragraph_id, sentence, word_count, end_type in chunk_by_sentence(data, maximum_length=paragraph_length): + + for paragraph_id, sentence, word_count, end_type in chunk_by_sentence( + data, maximum_length=paragraph_length + ): # Check if this sentence would exceed length limit if current_word_count > 0 and current_word_count + word_count > paragraph_length: # Yield current chunk @@ -25,9 +30,9 @@ def chunk_by_paragraph(data: str, paragraph_length: int = 1024, batch_paragraphs "chunk_index": chunk_index, "cut_type": last_cut_type, } - + yield chunk_dict - + # Start new chunk with current sentence paragraph_ids = [] current_chunk = "" @@ -37,7 +42,7 @@ def chunk_by_paragraph(data: str, paragraph_length: int = 1024, batch_paragraphs paragraph_ids.append(paragraph_id) current_chunk += sentence current_word_count += word_count - + # Handle end of paragraph if end_type in ("paragraph_end", "sentence_cut") and not batch_paragraphs: # For non-batch mode, yield each paragraph separately @@ -47,16 +52,16 @@ def chunk_by_paragraph(data: str, paragraph_length: int = 1024, batch_paragraphs "paragraph_ids": paragraph_ids, "chunk_id": uuid5(NAMESPACE_OID, current_chunk), "chunk_index": chunk_index, - "cut_type": end_type + "cut_type": end_type, } yield chunk_dict paragraph_ids = [] current_chunk = "" current_word_count = 0 chunk_index += 1 - + last_cut_type = end_type - + # Yield any remaining text if current_chunk: chunk_dict = { @@ -65,8 +70,7 @@ def chunk_by_paragraph(data: str, paragraph_length: int = 1024, batch_paragraphs "chunk_id": uuid5(NAMESPACE_OID, current_chunk), "paragraph_ids": paragraph_ids, "chunk_index": chunk_index, - "cut_type": "sentence_cut" if last_cut_type == "word" else last_cut_type + "cut_type": "sentence_cut" if last_cut_type == "word" else last_cut_type, } - - - yield chunk_dict \ No newline at end of file + + yield chunk_dict diff --git a/cognee/tasks/chunks/chunk_by_sentence.py b/cognee/tasks/chunks/chunk_by_sentence.py index c6848f066..128ed4047 100644 --- a/cognee/tasks/chunks/chunk_by_sentence.py +++ b/cognee/tasks/chunks/chunk_by_sentence.py @@ -1,10 +1,8 @@ - - - from uuid import uuid4 from typing import Optional from .chunk_by_word import chunk_by_word + def chunk_by_sentence(data: str, maximum_length: Optional[int] = None): sentence = "" paragraph_id = uuid4() @@ -16,7 +14,7 @@ def chunk_by_sentence(data: str, maximum_length: Optional[int] = None): # the word type is 'word', the word doesn't contain any letters # and words with the same characteristics connect it to a preceding # word with word_type 'paragraph_end' or 'sentence_end' - for (word, word_type) in chunk_by_word(data): + for word, word_type in chunk_by_word(data): sentence += word word_count += 1 @@ -28,7 +26,9 @@ def chunk_by_sentence(data: str, maximum_length: Optional[int] = None): word_type_state = word_type break - if word_type in ["paragraph_end", "sentence_end"] or (maximum_length and (word_count == maximum_length)): + if word_type in ["paragraph_end", "sentence_end"] or ( + maximum_length and (word_count == maximum_length) + ): yield (paragraph_id, sentence, word_count, word_type_state) sentence = "" word_count = 0 diff --git a/cognee/tasks/chunks/chunk_by_word.py b/cognee/tasks/chunks/chunk_by_word.py index ab4d8343e..c42d0cfa1 100644 --- a/cognee/tasks/chunks/chunk_by_word.py +++ b/cognee/tasks/chunks/chunk_by_word.py @@ -3,15 +3,16 @@ SENTENCE_ENDINGS = r"[.;!?…]" PARAGRAPH_ENDINGS = r"[\n\r]" + def is_real_paragraph_end(last_char: str, current_pos: int, text: str) -> bool: """ Determines if the current position represents a real paragraph ending. - + Args: last_char: The last processed character current_pos: Current position in the text text: The input text - + Returns: bool: True if this is a real paragraph end, False otherwise """ @@ -20,18 +21,19 @@ def is_real_paragraph_end(last_char: str, current_pos: int, text: str) -> bool: j = current_pos + 1 if j >= len(text): return False - + next_character = text[j] while j < len(text) and (re.match(PARAGRAPH_ENDINGS, next_character) or next_character == " "): j += 1 if j >= len(text): return False next_character = text[j] - + if next_character.isupper(): return True return False + def chunk_by_word(data: str): """ Chunks text into words and endings while preserving whitespace. @@ -40,32 +42,32 @@ def chunk_by_word(data: str): """ current_chunk = "" i = 0 - + while i < len(data): character = data[i] - + current_chunk += character - + if character == " ": yield (current_chunk, "word") current_chunk = "" i += 1 continue - - if re.match(SENTENCE_ENDINGS, character): + + if re.match(SENTENCE_ENDINGS, character): # Look ahead for whitespace next_i = i + 1 while next_i < len(data) and data[next_i] == " ": current_chunk += data[next_i] next_i += 1 - + is_paragraph_end = next_i < len(data) and re.match(PARAGRAPH_ENDINGS, data[next_i]) yield (current_chunk, "paragraph_end" if is_paragraph_end else "sentence_end") current_chunk = "" i = next_i continue - + i += 1 - + if current_chunk: - yield (current_chunk, "word") \ No newline at end of file + yield (current_chunk, "word") diff --git a/cognee/tasks/chunks/query_chunks.py b/cognee/tasks/chunks/query_chunks.py index 399528ee9..5a6d4f666 100644 --- a/cognee/tasks/chunks/query_chunks.py +++ b/cognee/tasks/chunks/query_chunks.py @@ -1,5 +1,6 @@ from cognee.infrastructure.databases.vector import get_vector_engine + async def query_chunks(query: str) -> list[dict]: """ Parameters: @@ -10,7 +11,7 @@ async def query_chunks(query: str) -> list[dict]: """ vector_engine = get_vector_engine() - found_chunks = await vector_engine.search("document_chunk_text", query, limit = 5) + found_chunks = await vector_engine.search("document_chunk_text", query, limit=5) chunks = [result.payload for result in found_chunks] diff --git a/cognee/tasks/chunks/remove_disconnected_chunks.py b/cognee/tasks/chunks/remove_disconnected_chunks.py index 4a36a33ec..60443ecfc 100644 --- a/cognee/tasks/chunks/remove_disconnected_chunks.py +++ b/cognee/tasks/chunks/remove_disconnected_chunks.py @@ -1,6 +1,7 @@ from cognee.infrastructure.databases.graph import get_graph_engine from cognee.modules.chunking.models.DocumentChunk import DocumentChunk + async def remove_disconnected_chunks(data_chunks: list[DocumentChunk]) -> list[DocumentChunk]: graph_engine = await get_graph_engine() @@ -9,10 +10,12 @@ async def remove_disconnected_chunks(data_chunks: list[DocumentChunk]) -> list[D obsolete_chunk_ids = [] for document_id in document_ids: - chunks = await graph_engine.get_successors(document_id, edge_label = "has_chunk") + chunks = await graph_engine.get_successors(document_id, edge_label="has_chunk") for chunk in chunks: - previous_chunks = await graph_engine.get_predecessors(chunk["uuid"], edge_label = "next_chunk") + previous_chunks = await graph_engine.get_predecessors( + chunk["uuid"], edge_label="next_chunk" + ) if len(previous_chunks) == 0: obsolete_chunk_ids.append(chunk["uuid"]) diff --git a/cognee/tasks/completion/__init__.py b/cognee/tasks/completion/__init__.py index 1bf0fa6bb..8a5aaa686 100644 --- a/cognee/tasks/completion/__init__.py +++ b/cognee/tasks/completion/__init__.py @@ -1 +1 @@ -from .query_completion import query_completion \ No newline at end of file +from .query_completion import query_completion diff --git a/cognee/tasks/completion/exceptions/__init__.py b/cognee/tasks/completion/exceptions/__init__.py index 5f80e6ecc..1530bf3f2 100644 --- a/cognee/tasks/completion/exceptions/__init__.py +++ b/cognee/tasks/completion/exceptions/__init__.py @@ -6,4 +6,4 @@ from .exceptions import ( NoRelevantDataFound, -) \ No newline at end of file +) diff --git a/cognee/tasks/completion/exceptions/exceptions.py b/cognee/tasks/completion/exceptions/exceptions.py index 9b64c01a6..bb4bcb0c8 100644 --- a/cognee/tasks/completion/exceptions/exceptions.py +++ b/cognee/tasks/completion/exceptions/exceptions.py @@ -1,11 +1,12 @@ from cognee.exceptions import CogneeApiError from fastapi import status + class NoRelevantDataFound(CogneeApiError): def __init__( - self, - message: str = "Search did not find any data.", - name: str = "NoRelevantDataFound", - status_code=status.HTTP_404_NOT_FOUND, + self, + message: str = "Search did not find any data.", + name: str = "NoRelevantDataFound", + status_code=status.HTTP_404_NOT_FOUND, ): - super().__init__(message, name, status_code) \ No newline at end of file + super().__init__(message, name, status_code) diff --git a/cognee/tasks/completion/query_completion.py b/cognee/tasks/completion/query_completion.py index 5324676f8..12e5168b0 100644 --- a/cognee/tasks/completion/query_completion.py +++ b/cognee/tasks/completion/query_completion.py @@ -14,7 +14,7 @@ async def query_completion(query: str) -> list: """ vector_engine = get_vector_engine() - found_chunks = await vector_engine.search("document_chunk_text", query, limit = 1) + found_chunks = await vector_engine.search("document_chunk_text", query, limit=1) if len(found_chunks) == 0: raise NoRelevantDataFound diff --git a/cognee/tasks/documents/check_permissions_on_documents.py b/cognee/tasks/documents/check_permissions_on_documents.py index ddedefe3f..7d18cbf3a 100644 --- a/cognee/tasks/documents/check_permissions_on_documents.py +++ b/cognee/tasks/documents/check_permissions_on_documents.py @@ -1,6 +1,7 @@ from cognee.modules.data.processing.document_types import Document from cognee.modules.users.permissions.methods import check_permission_on_documents + async def check_permissions_on_documents(documents: list[Document], user, permissions): document_ids = [document.id for document in documents] diff --git a/cognee/tasks/documents/classify_documents.py b/cognee/tasks/documents/classify_documents.py index 47beeb917..118da5738 100644 --- a/cognee/tasks/documents/classify_documents.py +++ b/cognee/tasks/documents/classify_documents.py @@ -54,13 +54,13 @@ async def classify_documents(data_documents: list[Data]) -> list[Document]: for data_item in data_documents: metadata = await get_metadata(data_item.id) document = EXTENSION_TO_DOCUMENT_CLASS[data_item.extension]( - id = data_item.id, - title = f"{data_item.name}.{data_item.extension}", - raw_data_location = data_item.raw_data_location, - name = data_item.name, - mime_type = data_item.mime_type, - metadata_id = metadata.id + id=data_item.id, + title=f"{data_item.name}.{data_item.extension}", + raw_data_location=data_item.raw_data_location, + name=data_item.name, + mime_type=data_item.mime_type, + metadata_id=metadata.id, ) documents.append(document) - + return documents diff --git a/cognee/tasks/documents/extract_chunks_from_documents.py b/cognee/tasks/documents/extract_chunks_from_documents.py index 423b87b69..437d2a3e4 100644 --- a/cognee/tasks/documents/extract_chunks_from_documents.py +++ b/cognee/tasks/documents/extract_chunks_from_documents.py @@ -1,7 +1,9 @@ from cognee.modules.data.processing.document_types.Document import Document -async def extract_chunks_from_documents(documents: list[Document], chunk_size: int = 1024, chunker = 'text_chunker'): +async def extract_chunks_from_documents( + documents: list[Document], chunk_size: int = 1024, chunker="text_chunker" +): for document in documents: - for document_chunk in document.read(chunk_size = chunk_size, chunker = chunker): + for document_chunk in document.read(chunk_size=chunk_size, chunker=chunker): yield document_chunk diff --git a/cognee/tasks/graph/extract_graph_from_code.py b/cognee/tasks/graph/extract_graph_from_code.py index 159e9baa4..8688b0af5 100644 --- a/cognee/tasks/graph/extract_graph_from_code.py +++ b/cognee/tasks/graph/extract_graph_from_code.py @@ -5,12 +5,13 @@ from cognee.modules.chunking.models.DocumentChunk import DocumentChunk from cognee.tasks.storage import add_data_points + async def extract_graph_from_code(data_chunks: list[DocumentChunk], graph_model: Type[BaseModel]): chunk_graphs = await asyncio.gather( *[extract_content_graph(chunk.text, graph_model) for chunk in data_chunks] ) - for (chunk_index, chunk) in enumerate(data_chunks): + for chunk_index, chunk in enumerate(data_chunks): chunk_graph = chunk_graphs[chunk_index] await add_data_points(chunk_graph.nodes) diff --git a/cognee/tasks/graph/extract_graph_from_data.py b/cognee/tasks/graph/extract_graph_from_data.py index c6a613105..b05c55a9e 100644 --- a/cognee/tasks/graph/extract_graph_from_data.py +++ b/cognee/tasks/graph/extract_graph_from_data.py @@ -13,9 +13,7 @@ from cognee.tasks.storage import add_data_points -async def extract_graph_from_data( - data_chunks: list[DocumentChunk], graph_model: Type[BaseModel] -): +async def extract_graph_from_data(data_chunks: list[DocumentChunk], graph_model: Type[BaseModel]): chunk_graphs = await asyncio.gather( *[extract_content_graph(chunk.text, graph_model) for chunk in data_chunks] ) diff --git a/cognee/tasks/graph/infer_data_ontology.py b/cognee/tasks/graph/infer_data_ontology.py index 4e11cd9af..ca7807e1e 100644 --- a/cognee/tasks/graph/infer_data_ontology.py +++ b/cognee/tasks/graph/infer_data_ontology.py @@ -1,4 +1,4 @@ -""" This module contains the OntologyEngine class which is responsible for adding graph ontology from a JSON or CSV file. """ +"""This module contains the OntologyEngine class which is responsible for adding graph ontology from a JSON or CSV file.""" import csv import json @@ -20,7 +20,9 @@ from cognee.infrastructure.databases.graph.get_graph_engine import get_graph_engine from cognee.infrastructure.files.utils.extract_text_from_file import extract_text_from_file from cognee.infrastructure.files.utils.guess_file_type import guess_file_type, FileTypeException -from cognee.modules.data.extraction.knowledge_graph.add_model_class_to_graph import add_model_class_to_graph +from cognee.modules.data.extraction.knowledge_graph.add_model_class_to_graph import ( + add_model_class_to_graph, +) from cognee.tasks.graph.models import NodeModel, GraphOntology from cognee.shared.data_models import KnowledgeGraph from cognee.modules.engine.utils import generate_node_id, generate_node_name @@ -39,20 +41,26 @@ async def extract_ontology(content: str, response_model: Type[BaseModel]): class OntologyEngine: - async def flatten_model(self, model: NodeModel, parent_id: Optional[str] = None) -> Dict[str, Any]: + async def flatten_model( + self, model: NodeModel, parent_id: Optional[str] = None + ) -> Dict[str, Any]: """Flatten the model to a dictionary.""" result = model.dict() result["parent_id"] = parent_id if model.default_relationship: - result.update({ - "relationship_type": model.default_relationship.type, - "relationship_source": model.default_relationship.source, - "relationship_target": model.default_relationship.target - }) + result.update( + { + "relationship_type": model.default_relationship.type, + "relationship_source": model.default_relationship.source, + "relationship_target": model.default_relationship.target, + } + ) return result - async def recursive_flatten(self, items: Union[List[Dict[str, Any]], Dict[str, Any]], parent_id: Optional[str] = None) -> List[Dict[str, Any]]: - """Recursively flatten the items. """ + async def recursive_flatten( + self, items: Union[List[Dict[str, Any]], Dict[str, Any]], parent_id: Optional[str] = None + ) -> List[Dict[str, Any]]: + """Recursively flatten the items.""" flat_list = [] if isinstance(items, list): @@ -80,8 +88,10 @@ async def load_data(self, file_path: str) -> Union[List[Dict[str, Any]], Dict[st else: raise IngestionError(message="Unsupported file format") except Exception as e: - raise IngestionError(message=f"Failed to load data from {file_path}: {e}", - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) + raise IngestionError( + message=f"Failed to load data from {file_path}: {e}", + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + ) async def add_graph_ontology(self, file_path: str = None, documents: list = None): """Add graph ontology from a JSON or CSV file or infer from documents content.""" @@ -109,31 +119,43 @@ async def add_graph_ontology(self, file_path: str = None, documents: list = None initial_chunks_and_ids.append({base_file.id: chunks_with_ids}) except FileTypeException: - logger.warning("File (%s) has an unknown file type. We are skipping it.", file["id"]) - + logger.warning( + "File (%s) has an unknown file type. We are skipping it.", file["id"] + ) ontology = await extract_ontology(str(initial_chunks_and_ids), GraphOntology) graph_client = await get_graph_engine() - await graph_client.add_nodes([(node.id, dict( - uuid = generate_node_id(node.id), - name = generate_node_name(node.name), - type = generate_node_id(node.id), - description = node.description, - updated_at = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"), - )) for node in ontology.nodes]) - - await graph_client.add_edges(( - generate_node_id(edge.source_id), - generate_node_id(edge.target_id), - edge.relationship_type, - dict( - source_node_id = generate_node_id(edge.source_id), - target_node_id = generate_node_id(edge.target_id), - relationship_name = edge.relationship_type, - updated_at = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"), - ), - ) for edge in ontology.edges) + await graph_client.add_nodes( + [ + ( + node.id, + dict( + uuid=generate_node_id(node.id), + name=generate_node_name(node.name), + type=generate_node_id(node.id), + description=node.description, + updated_at=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"), + ), + ) + for node in ontology.nodes + ] + ) + + await graph_client.add_edges( + ( + generate_node_id(edge.source_id), + generate_node_id(edge.target_id), + edge.relationship_type, + dict( + source_node_id=generate_node_id(edge.source_id), + target_node_id=generate_node_id(edge.target_id), + relationship_name=edge.relationship_type, + updated_at=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"), + ), + ) + for edge in ontology.edges + ) else: dataset_level_information = documents[0][1] @@ -152,17 +174,23 @@ async def add_graph_ontology(self, file_path: str = None, documents: list = None if node_id in valid_ids: await graph_client.add_node(node_id, node_data) if node_id not in valid_ids: - raise EntityNotFoundError(message=f"Node ID {node_id} not found in the dataset") - if pd.notna(row.get("relationship_source")) and pd.notna(row.get("relationship_target")): + raise EntityNotFoundError( + message=f"Node ID {node_id} not found in the dataset" + ) + if pd.notna(row.get("relationship_source")) and pd.notna( + row.get("relationship_target") + ): await graph_client.add_edge( row["relationship_source"], row["relationship_target"], relationship_name=row["relationship_type"], - edge_properties = { + edge_properties={ "source_node_id": row["relationship_source"], "target_node_id": row["relationship_target"], "relationship_name": row["relationship_type"], - "updated_at": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"), + "updated_at": datetime.now(timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ), }, ) @@ -171,10 +199,10 @@ async def add_graph_ontology(self, file_path: str = None, documents: list = None raise RuntimeError(f"Failed to add graph ontology from {file_path}: {e}") from e -async def infer_data_ontology(documents, ontology_model = KnowledgeGraph, root_node_id = None): +async def infer_data_ontology(documents, ontology_model=KnowledgeGraph, root_node_id=None): if ontology_model == KnowledgeGraph: ontology_engine = OntologyEngine() - root_node_id = await ontology_engine.add_graph_ontology(documents = documents) + root_node_id = await ontology_engine.add_graph_ontology(documents=documents) else: graph_engine = await get_graph_engine() await add_model_class_to_graph(ontology_model, graph_engine) diff --git a/cognee/tasks/graph/models.py b/cognee/tasks/graph/models.py index 5b1108e6a..bfffb0262 100644 --- a/cognee/tasks/graph/models.py +++ b/cognee/tasks/graph/models.py @@ -1,31 +1,36 @@ from typing import Any, Dict, List, Optional, Union from pydantic import BaseModel, Field + class RelationshipModel(BaseModel): type: str source: str target: str + class NodeModel(BaseModel): node_id: str name: str default_relationship: Optional[RelationshipModel] = None children: List[Union[Dict[str, Any], "NodeModel"]] = Field(default_factory=list) + NodeModel.model_rebuild() class OntologyNode(BaseModel): - id: str = Field(..., description = "Unique identifier made from node name.") + id: str = Field(..., description="Unique identifier made from node name.") name: str description: str + class OntologyEdge(BaseModel): id: str source_id: str target_id: str relationship_type: str + class GraphOntology(BaseModel): nodes: list[OntologyNode] - edges: list[OntologyEdge] \ No newline at end of file + edges: list[OntologyEdge] diff --git a/cognee/tasks/graph/query_graph_connections.py b/cognee/tasks/graph/query_graph_connections.py index 4020ddd13..faec94546 100644 --- a/cognee/tasks/graph/query_graph_connections.py +++ b/cognee/tasks/graph/query_graph_connections.py @@ -2,7 +2,8 @@ from cognee.infrastructure.databases.graph import get_graph_engine from cognee.infrastructure.databases.vector import get_vector_engine -async def query_graph_connections(query: str, exploration_levels = 1) -> list[(str, str, str)]: + +async def query_graph_connections(query: str, exploration_levels=1) -> list[(str, str, str)]: """ Find the neighbours of a given node in the graph and return formed sentences. @@ -27,8 +28,8 @@ async def query_graph_connections(query: str, exploration_levels = 1) -> list[(s else: vector_engine = get_vector_engine() results = await asyncio.gather( - vector_engine.search("entity_name", query_text = query, limit = 5), - vector_engine.search("entity_type_name", query_text = query, limit = 5), + vector_engine.search("entity_name", query_text=query, limit=5), + vector_engine.search("entity_type_name", query_text=query, limit=5), ) results = [*results[0], *results[1]] relevant_results = [result for result in results if result.score < 0.5][:5] @@ -44,7 +45,6 @@ async def query_graph_connections(query: str, exploration_levels = 1) -> list[(s for neighbours in node_connections_results: node_connections.extend(neighbours) - unique_node_connections_map = {} unique_node_connections = [] for node_connection in node_connections: @@ -58,5 +58,4 @@ async def query_graph_connections(query: str, exploration_levels = 1) -> list[(s unique_node_connections.append(node_connection) - return unique_node_connections diff --git a/cognee/tasks/ingestion/get_dlt_destination.py b/cognee/tasks/ingestion/get_dlt_destination.py index 12042c75b..2de0d7f0d 100644 --- a/cognee/tasks/ingestion/get_dlt_destination.py +++ b/cognee/tasks/ingestion/get_dlt_destination.py @@ -6,6 +6,7 @@ from cognee.infrastructure.databases.relational import get_relational_config + @lru_cache def get_dlt_destination() -> Union[type[dlt.destinations.sqlalchemy], None]: """ @@ -21,24 +22,24 @@ def get_dlt_destination() -> Union[type[dlt.destinations.sqlalchemy], None]: # When sqlite is the database provider hostname, port, username and password should not be forwarded. # The database is found by combining the path location and the database name destination = dlt.destinations.sqlalchemy( - credentials = { - "database": os.path.join(relational_config.db_path, relational_config.db_name), - "drivername": relational_config.db_provider, - }, - ) + credentials={ + "database": os.path.join(relational_config.db_path, relational_config.db_name), + "drivername": relational_config.db_provider, + }, + ) elif relational_config.db_provider == "postgres": # The dlt library doesn't accept postgres as the drivername, it only accepts postgresql destination = dlt.destinations.sqlalchemy( - credentials = { - "host": relational_config.db_host, - "port": relational_config.db_port, - "username": relational_config.db_username, - "password": relational_config.db_password, - "database": relational_config.db_name, - "drivername": "postgresql", - }, - ) + credentials={ + "host": relational_config.db_host, + "port": relational_config.db_port, + "username": relational_config.db_username, + "password": relational_config.db_password, + "database": relational_config.db_name, + "drivername": "postgresql", + }, + ) else: destination = None - + return destination diff --git a/cognee/tasks/ingestion/ingest_data.py b/cognee/tasks/ingestion/ingest_data.py index 9418d035b..cf7dd38ad 100644 --- a/cognee/tasks/ingestion/ingest_data.py +++ b/cognee/tasks/ingestion/ingest_data.py @@ -9,18 +9,19 @@ from cognee.modules.users.permissions.methods import give_permission_on_document from .get_dlt_destination import get_dlt_destination + async def ingest_data(file_paths: list[str], dataset_name: str, user: User): destination = get_dlt_destination() pipeline = dlt.pipeline( - pipeline_name = "file_load_from_filesystem", - destination = destination, + pipeline_name="file_load_from_filesystem", + destination=destination, ) - @dlt.resource(standalone = True, merge_key = "id") + @dlt.resource(standalone=True, merge_key="id") async def data_resources(file_paths: str): for file_path in file_paths: - with open(file_path.replace("file://", ""), mode = "rb") as file: + with open(file_path.replace("file://", ""), mode="rb") as file: classified_data = ingestion.classify(file) data_id = ingestion.identify(classified_data) file_metadata = classified_data.get_metadata() @@ -39,39 +40,39 @@ async def data_storing(table_name, dataset_name, user: User): # Read metadata stored with dlt files_metadata = await db_engine.get_all_data_from_table(table_name, dataset_name) for file_metadata in files_metadata: - from sqlalchemy import select - from cognee.modules.data.models import Data - dataset = await create_dataset(dataset_name, user.id, session) + from sqlalchemy import select + from cognee.modules.data.models import Data - data = (await session.execute( - select(Data).filter(Data.id == UUID(file_metadata["id"])) - )).scalar_one_or_none() + dataset = await create_dataset(dataset_name, user.id, session) - if data is not None: - data.name = file_metadata["name"] - data.raw_data_location = file_metadata["file_path"] - data.extension = file_metadata["extension"] - data.mime_type = file_metadata["mime_type"] + data = ( + await session.execute(select(Data).filter(Data.id == UUID(file_metadata["id"]))) + ).scalar_one_or_none() - await session.merge(data) - await session.commit() - else: - data = Data( - id = UUID(file_metadata["id"]), - name = file_metadata["name"], - raw_data_location = file_metadata["file_path"], - extension = file_metadata["extension"], - mime_type = file_metadata["mime_type"], - ) + if data is not None: + data.name = file_metadata["name"] + data.raw_data_location = file_metadata["file_path"] + data.extension = file_metadata["extension"] + data.mime_type = file_metadata["mime_type"] - dataset.data.append(data) - await session.commit() + await session.merge(data) + await session.commit() + else: + data = Data( + id=UUID(file_metadata["id"]), + name=file_metadata["name"], + raw_data_location=file_metadata["file_path"], + extension=file_metadata["extension"], + mime_type=file_metadata["mime_type"], + ) - await give_permission_on_document(user, UUID(file_metadata["id"]), "read") - await give_permission_on_document(user, UUID(file_metadata["id"]), "write") + dataset.data.append(data) + await session.commit() + await give_permission_on_document(user, UUID(file_metadata["id"]), "read") + await give_permission_on_document(user, UUID(file_metadata["id"]), "write") - send_telemetry("cognee.add EXECUTION STARTED", user_id = user.id) + send_telemetry("cognee.add EXECUTION STARTED", user_id=user.id) db_engine = get_relational_engine() @@ -82,9 +83,9 @@ async def data_storing(table_name, dataset_name, user: User): # Sqlite doesn't support schemas run_info = pipeline.run( data_resources(file_paths), - table_name = "file_metadata", - dataset_name = "main", - write_disposition = "merge", + table_name="file_metadata", + dataset_name="main", + write_disposition="merge", ) else: run_info = pipeline.run( @@ -95,6 +96,6 @@ async def data_storing(table_name, dataset_name, user: User): ) await data_storing("file_metadata", dataset_name, user) - send_telemetry("cognee.add EXECUTION COMPLETED", user_id = user.id) + send_telemetry("cognee.add EXECUTION COMPLETED", user_id=user.id) return run_info diff --git a/cognee/tasks/ingestion/ingest_data_with_metadata.py b/cognee/tasks/ingestion/ingest_data_with_metadata.py index c6b42f482..04396485c 100644 --- a/cognee/tasks/ingestion/ingest_data_with_metadata.py +++ b/cognee/tasks/ingestion/ingest_data_with_metadata.py @@ -19,8 +19,8 @@ async def ingest_data_with_metadata(data: Any, dataset_name: str, user: User): destination = get_dlt_destination() pipeline = dlt.pipeline( - pipeline_name = "file_load_from_filesystem", - destination = destination, + pipeline_name="file_load_from_filesystem", + destination=destination, ) @dlt.resource(standalone=True, primary_key="id", merge_key="id") @@ -49,14 +49,12 @@ async def data_storing(data: Any, dataset_name: str, user: User): # Process data for data_item in data: - file_path = await save_data_item_with_metadata_to_storage( - data_item, dataset_name - ) + file_path = await save_data_item_with_metadata_to_storage(data_item, dataset_name) file_paths.append(file_path) # Ingest data and add metadata - with open(file_path.replace("file://", ""), mode = "rb") as file: + with open(file_path.replace("file://", ""), mode="rb") as file: classified_data = ingestion.classify(file) # data_id is the hash of file contents + owner id to avoid duplicate data @@ -88,19 +86,22 @@ async def data_storing(data: Any, dataset_name: str, user: User): await session.merge(data_point) else: data_point = Data( - id = data_id, - name = file_metadata["name"], - raw_data_location = file_metadata["file_path"], - extension = file_metadata["extension"], - mime_type = file_metadata["mime_type"], - owner_id = user.id, - content_hash = file_metadata["content_hash"], + id=data_id, + name=file_metadata["name"], + raw_data_location=file_metadata["file_path"], + extension=file_metadata["extension"], + mime_type=file_metadata["mime_type"], + owner_id=user.id, + content_hash=file_metadata["content_hash"], ) # Check if data is already in dataset dataset_data = ( - await session.execute(select(DatasetData).filter(DatasetData.data_id == data_id, - DatasetData.dataset_id == dataset.id)) + await session.execute( + select(DatasetData).filter( + DatasetData.data_id == data_id, DatasetData.dataset_id == dataset.id + ) + ) ).scalar_one_or_none() # If data is not present in dataset add it if dataset_data is None: diff --git a/cognee/tasks/ingestion/resolve_data_directories.py b/cognee/tasks/ingestion/resolve_data_directories.py index 980756805..e549688d8 100644 --- a/cognee/tasks/ingestion/resolve_data_directories.py +++ b/cognee/tasks/ingestion/resolve_data_directories.py @@ -1,7 +1,10 @@ import os from typing import List, Union, BinaryIO -async def resolve_data_directories(data: Union[BinaryIO, List[BinaryIO], str, List[str]], include_subdirectories: bool = True): + +async def resolve_data_directories( + data: Union[BinaryIO, List[BinaryIO], str, List[str]], include_subdirectories: bool = True +): """ Resolves directories by replacing them with their contained files. @@ -28,10 +31,14 @@ async def resolve_data_directories(data: Union[BinaryIO, List[BinaryIO], str, Li else: # Add all files (not subdirectories) in the directory resolved_data.extend( - [os.path.join(item, f) for f in os.listdir(item) if os.path.isfile(os.path.join(item, f))] + [ + os.path.join(item, f) + for f in os.listdir(item) + if os.path.isfile(os.path.join(item, f)) + ] ) - else: # If it's a file or text add it directly + else: # If it's a file or text add it directly resolved_data.append(item) - else: # If it's not a string add it directly + else: # If it's not a string add it directly resolved_data.append(item) return resolved_data diff --git a/cognee/tasks/ingestion/save_data_item_to_storage.py b/cognee/tasks/ingestion/save_data_item_to_storage.py index 88d499e74..9191f7ebc 100644 --- a/cognee/tasks/ingestion/save_data_item_to_storage.py +++ b/cognee/tasks/ingestion/save_data_item_to_storage.py @@ -3,8 +3,8 @@ from cognee.modules.ingestion.exceptions import IngestionError from cognee.modules.ingestion import save_data_to_file -def save_data_item_to_storage(data_item: Union[BinaryIO, str], dataset_name: str) -> str: +def save_data_item_to_storage(data_item: Union[BinaryIO, str], dataset_name: str) -> str: # data is a file object coming from upload. if hasattr(data_item, "file"): file_path = save_data_to_file(data_item.file, filename=data_item.filename) @@ -19,4 +19,4 @@ def save_data_item_to_storage(data_item: Union[BinaryIO, str], dataset_name: str else: raise IngestionError(message=f"Data type not supported: {type(data_item)}") - return file_path \ No newline at end of file + return file_path diff --git a/cognee/tasks/ingestion/save_data_item_with_metadata_to_storage.py b/cognee/tasks/ingestion/save_data_item_with_metadata_to_storage.py index 06dde11bd..92697abb7 100644 --- a/cognee/tasks/ingestion/save_data_item_with_metadata_to_storage.py +++ b/cognee/tasks/ingestion/save_data_item_with_metadata_to_storage.py @@ -7,7 +7,6 @@ async def save_data_item_with_metadata_to_storage( data_item: Union[BinaryIO, str, Any], dataset_name: str ) -> str: - if "llama_index" in str(type(data_item)): # Dynamic import is used because the llama_index module is optional. from .transform_data import get_data_from_llama_index @@ -16,9 +15,7 @@ async def save_data_item_with_metadata_to_storage( # data is a file object coming from upload. elif hasattr(data_item, "file"): - file_path = save_data_to_file( - data_item.file, filename=data_item.filename - ) + file_path = save_data_to_file(data_item.file, filename=data_item.filename) elif isinstance(data_item, str): # data is a file path diff --git a/cognee/tasks/ingestion/save_data_to_storage.py b/cognee/tasks/ingestion/save_data_to_storage.py index 85eb81582..a56857261 100644 --- a/cognee/tasks/ingestion/save_data_to_storage.py +++ b/cognee/tasks/ingestion/save_data_to_storage.py @@ -1,6 +1,7 @@ from typing import Union, BinaryIO from cognee.tasks.ingestion.save_data_item_to_storage import save_data_item_to_storage + def save_data_to_storage(data: Union[BinaryIO, str], dataset_name) -> list[str]: if not isinstance(data, list): # Convert data to a list as we work with lists further down. diff --git a/cognee/tasks/ingestion/transform_data.py b/cognee/tasks/ingestion/transform_data.py index 898ac6e71..cc75c7a65 100644 --- a/cognee/tasks/ingestion/transform_data.py +++ b/cognee/tasks/ingestion/transform_data.py @@ -3,16 +3,17 @@ from cognee.modules.ingestion import save_data_to_file from typing import Union + def get_data_from_llama_index(data_point: Union[Document, ImageDocument], dataset_name: str) -> str: # Specific type checking is used to ensure it's not a child class from Document - if type(data_point) == Document: + if isinstance(data_point, Document) and type(data_point) is Document: file_path = data_point.metadata.get("file_path") if file_path is None: file_path = save_data_to_file(data_point.text) return file_path return file_path - elif type(data_point) == ImageDocument: + elif isinstance(data_point, ImageDocument) and type(data_point) is ImageDocument: if data_point.image_path is None: file_path = save_data_to_file(data_point.text) return file_path - return data_point.image_path \ No newline at end of file + return data_point.image_path diff --git a/cognee/tasks/repo_processor/__init__.py b/cognee/tasks/repo_processor/__init__.py index fa754028e..6dc032547 100644 --- a/cognee/tasks/repo_processor/__init__.py +++ b/cognee/tasks/repo_processor/__init__.py @@ -1,8 +1,7 @@ -import logging - -logger = logging.getLogger("task:repo_processor") - from .enrich_dependency_graph import enrich_dependency_graph from .expand_dependency_graph import expand_dependency_graph from .get_non_code_files import get_data_list_for_user, get_non_py_files from .get_repo_file_dependencies import get_repo_file_dependencies +import logging + +logger = logging.getLogger("task:repo_processor") diff --git a/cognee/tasks/repo_processor/enrich_dependency_graph.py b/cognee/tasks/repo_processor/enrich_dependency_graph.py index 8cce7c42a..5f2b91d95 100644 --- a/cognee/tasks/repo_processor/enrich_dependency_graph.py +++ b/cognee/tasks/repo_processor/enrich_dependency_graph.py @@ -8,7 +8,9 @@ from cognee.infrastructure.databases.graph import get_graph_engine -def topologically_sort_subgraph(subgraph_node_to_indegree: Dict[str, int], graph: nx.DiGraph) -> List[str]: +def topologically_sort_subgraph( + subgraph_node_to_indegree: Dict[str, int], graph: nx.DiGraph +) -> List[str]: """Performs a topological sort on a subgraph based on node indegrees.""" results = [] remaining_nodes = subgraph_node_to_indegree.copy() @@ -33,13 +35,8 @@ def topologically_sort(graph: nx.DiGraph) -> List[str]: topological_order = [] for subgraph in subgraphs: - node_to_indegree = { - node: len(list(subgraph.successors(node))) - for node in subgraph.nodes - } - topological_order.extend( - topologically_sort_subgraph(node_to_indegree, subgraph) - ) + node_to_indegree = {node: len(list(subgraph.successors(node))) for node in subgraph.nodes} + topological_order.extend(topologically_sort_subgraph(node_to_indegree, subgraph)) return topological_order @@ -62,7 +59,7 @@ async def node_enrich_and_connect( graph_engine = await get_graph_engine() for desc_id in node_descendants: - if desc_id not in topological_order[:topological_rank + 1]: + if desc_id not in topological_order[: topological_rank + 1]: continue desc = None @@ -83,7 +80,9 @@ async def node_enrich_and_connect( node.depends_directly_on.extend(new_connections) -async def enrich_dependency_graph(data_points: list[DataPoint]) -> AsyncGenerator[list[DataPoint], None]: +async def enrich_dependency_graph( + data_points: list[DataPoint], +) -> AsyncGenerator[list[DataPoint], None]: """Enriches the graph with topological ranks and 'depends_on' edges.""" nodes = [] edges = [] @@ -94,9 +93,9 @@ async def enrich_dependency_graph(data_points: list[DataPoint]) -> AsyncGenerato for data_point in data_points: graph_nodes, graph_edges = await get_graph_from_model( data_point, - added_nodes = added_nodes, - added_edges = added_edges, - visited_properties = visited_properties, + added_nodes=added_nodes, + added_edges=added_edges, + visited_properties=visited_properties, ) nodes.extend(graph_nodes) edges.extend(graph_edges) @@ -122,7 +121,7 @@ async def enrich_dependency_graph(data_points: list[DataPoint]) -> AsyncGenerato data_points_map = {data_point.id: data_point for data_point in data_points} # data_points_futures = [] - for data_point in tqdm(data_points, desc = "Enriching dependency graph", unit = "data_point"): + for data_point in tqdm(data_points, desc="Enriching dependency graph", unit="data_point"): if data_point.id not in node_rank_map: continue @@ -131,7 +130,7 @@ async def enrich_dependency_graph(data_points: list[DataPoint]) -> AsyncGenerato await node_enrich_and_connect(graph, topological_order, data_point, data_points_map) yield data_point - + # await asyncio.gather(*data_points_futures) # return data_points diff --git a/cognee/tasks/repo_processor/expand_dependency_graph.py b/cognee/tasks/repo_processor/expand_dependency_graph.py index 43a451bd6..de26fe8d4 100644 --- a/cognee/tasks/repo_processor/expand_dependency_graph.py +++ b/cognee/tasks/repo_processor/expand_dependency_graph.py @@ -1,11 +1,13 @@ from typing import AsyncGenerator from uuid import NAMESPACE_OID, uuid5 + # from tqdm import tqdm from cognee.infrastructure.engine import DataPoint from cognee.shared.CodeGraphEntities import CodeFile, CodePart from cognee.tasks.repo_processor.extract_code_parts import extract_code_parts from cognee.tasks.repo_processor import logger + def _add_code_parts_nodes_and_edges(code_file: CodeFile, part_type, code_parts) -> None: """Add code part nodes and edges for a specific part type.""" if not code_parts: @@ -21,12 +23,14 @@ def _add_code_parts_nodes_and_edges(code_file: CodeFile, part_type, code_parts) part_node_id = uuid5(NAMESPACE_OID, f"{code_file.id}_{part_type}_{idx}") - part_nodes.append(CodePart( - id = part_node_id, - type = part_type, - # part_of = code_file, - source_code = code_part, - )) + part_nodes.append( + CodePart( + id=part_node_id, + type=part_type, + # part_of = code_file, + source_code=code_part, + ) + ) # graph.add_node(part_node_id, source_code=code_part, node_type=part_type) # graph.add_edge(parent_node_id, part_node_id, relation="contains") @@ -54,7 +58,9 @@ def _process_single_node(code_file: CodeFile) -> None: _add_code_parts_nodes_and_edges(code_file, part_type, code_parts) -async def expand_dependency_graph(data_points: list[DataPoint]) -> AsyncGenerator[list[DataPoint], None]: +async def expand_dependency_graph( + data_points: list[DataPoint], +) -> AsyncGenerator[list[DataPoint], None]: """Process Python file nodes, adding code part nodes and edges.""" # for data_point in tqdm(data_points, desc = "Expand dependency graph", unit = "data_point"): for data_point in data_points: diff --git a/cognee/tasks/repo_processor/extract_code_parts.py b/cognee/tasks/repo_processor/extract_code_parts.py index d772c73f5..76cfef538 100644 --- a/cognee/tasks/repo_processor/extract_code_parts.py +++ b/cognee/tasks/repo_processor/extract_code_parts.py @@ -9,19 +9,19 @@ def _extract_parts_from_module(module, parts_dict: Dict[str, List[str]]) -> Dict current_top_level_code = [] child_to_code_type = { - 'classdef': "classes", - 'funcdef': "functions", - 'import_name': "imports", - 'import_from': "imports", + "classdef": "classes", + "funcdef": "functions", + "import_name": "imports", + "import_from": "imports", } for child in module.children: - if child.type == 'simple_stmt': + if child.type == "simple_stmt": current_top_level_code.append(child.get_code()) continue if current_top_level_code: - parts_dict["top_level_code"].append('\n'.join(current_top_level_code)) + parts_dict["top_level_code"].append("\n".join(current_top_level_code)) current_top_level_code = [] if child.type in child_to_code_type: @@ -29,10 +29,10 @@ def _extract_parts_from_module(module, parts_dict: Dict[str, List[str]]) -> Dict parts_dict[code_type].append(child.get_code()) if current_top_level_code: - parts_dict["top_level_code"].append('\n'.join(current_top_level_code)) + parts_dict["top_level_code"].append("\n".join(current_top_level_code)) if parts_dict["imports"]: - parts_dict["imports"] = ['\n'.join(parts_dict["imports"])] + parts_dict["imports"] = ["\n".join(parts_dict["imports"])] return parts_dict diff --git a/cognee/tasks/repo_processor/get_local_dependencies.py b/cognee/tasks/repo_processor/get_local_dependencies.py index fb4c68710..b443829c9 100644 --- a/cognee/tasks/repo_processor/get_local_dependencies.py +++ b/cognee/tasks/repo_processor/get_local_dependencies.py @@ -29,15 +29,15 @@ def _get_code_entities(node: parso.tree.NodeOrLeaf) -> List[Dict[str, any]]: """ code_entity_list = [] - if not hasattr(node, 'children'): + if not hasattr(node, "children"): return code_entity_list - name_nodes = (child for child in node.children if child.type == 'name') + name_nodes = (child for child in node.children if child.type == "name") for name_node in name_nodes: code_entity = { - 'name': name_node.value, - 'line': name_node.start_pos[0], - 'column': name_node.start_pos[1] + "name": name_node.value, + "line": name_node.start_pos[0], + "column": name_node.start_pos[1], } code_entity_list.append(code_entity) @@ -97,7 +97,9 @@ async def _extract_dependencies(script_path: str) -> List[str]: return sorted(str_paths) -async def get_local_script_dependencies(script_path: str, repo_path: Optional[str] = None) -> List[str]: +async def get_local_script_dependencies( + script_path: str, repo_path: Optional[str] = None +) -> List[str]: """ Extract and return a list of unique module paths that the script depends on. """ @@ -117,7 +119,9 @@ async def get_local_script_dependencies(script_path: str, repo_path: Optional[st return await _extract_dependencies(script_path) if not script_path.is_relative_to(repo_path): - logger.warning(f"Script {script_path} not in repo {repo_path}. Proceeding without repo_path.") + logger.warning( + f"Script {script_path} not in repo {repo_path}. Proceeding without repo_path." + ) return await _extract_dependencies(script_path) with add_sys_path(str(repo_path)): diff --git a/cognee/tasks/repo_processor/get_non_code_files.py b/cognee/tasks/repo_processor/get_non_code_files.py index 671b998d9..9c69afd00 100644 --- a/cognee/tasks/repo_processor/get_non_code_files.py +++ b/cognee/tasks/repo_processor/get_non_code_files.py @@ -6,8 +6,7 @@ from cognee.infrastructure.engine import DataPoint from cognee.modules.data.methods import get_datasets from cognee.modules.data.methods.get_dataset_data import get_dataset_data -from cognee.modules.data.methods.get_datasets_by_name import \ - get_datasets_by_name +from cognee.modules.data.methods.get_datasets_by_name import get_datasets_by_name from cognee.modules.data.models import Data from cognee.modules.data.operations.write_metadata import write_metadata from cognee.modules.ingestion.data_types import BinaryData @@ -21,8 +20,13 @@ async def get_non_py_files(repo_path): return {} IGNORED_PATTERNS = { - '.git', '__pycache__', '*.pyc', '*.pyo', '*.pyd', - 'node_modules', '*.egg-info' + ".git", + "__pycache__", + "*.pyc", + "*.pyo", + "*.pyd", + "node_modules", + "*.egg-info", } def should_process(path): @@ -30,7 +34,8 @@ def should_process(path): non_py_files_paths = [ os.path.join(root, file) - for root, _, files in os.walk(repo_path) for file in files + for root, _, files in os.walk(repo_path) + for file in files if not file.endswith(".py") and should_process(os.path.join(root, file)) ] return non_py_files_paths @@ -45,4 +50,4 @@ async def get_data_list_for_user(_, dataset_name, user): for dataset in datasets: data_docs: list[Data] = await get_dataset_data(dataset_id=dataset.id) data_documents.extend(data_docs) - return data_documents \ No newline at end of file + return data_documents diff --git a/cognee/tasks/repo_processor/get_repo_file_dependencies.py b/cognee/tasks/repo_processor/get_repo_file_dependencies.py index b54c1f152..f9be53d7d 100644 --- a/cognee/tasks/repo_processor/get_repo_file_dependencies.py +++ b/cognee/tasks/repo_processor/get_repo_file_dependencies.py @@ -7,8 +7,7 @@ import aiofiles from cognee.shared.CodeGraphEntities import CodeFile, Repository -from cognee.tasks.repo_processor.get_local_dependencies import \ - get_local_script_dependencies +from cognee.tasks.repo_processor.get_local_dependencies import get_local_script_dependencies async def get_py_path_and_source(file_path): @@ -28,23 +27,27 @@ async def get_py_files_dict(repo_path): py_files_paths = ( os.path.join(root, file) - for root, _, files in os.walk(repo_path) for file in files if file.endswith(".py") + for root, _, files in os.walk(repo_path) + for file in files + if file.endswith(".py") ) py_files_dict = {} for file_path in py_files_paths: absolute_path = os.path.abspath(file_path) - + if os.path.getsize(absolute_path) == 0: continue - + relative_path, source_code = await get_py_path_and_source(absolute_path) py_files_dict[relative_path] = {"source_code": source_code} return py_files_dict -def get_edge(file_path: str, dependency: str, repo_path: str, relative_paths: bool = False) -> tuple: +def get_edge( + file_path: str, dependency: str, repo_path: str, relative_paths: bool = False +) -> tuple: if relative_paths: file_path = os.path.relpath(file_path, repo_path) dependency = os.path.relpath(dependency, repo_path) @@ -58,22 +61,23 @@ def run_coroutine(coroutine_func, *args, **kwargs): loop.close() return result + async def get_repo_file_dependencies(repo_path: str) -> AsyncGenerator[list, None]: """Generate a dependency graph for Python files in the given repository path.""" - + if not os.path.exists(repo_path): raise FileNotFoundError(f"Repository path {repo_path} does not exist.") - + py_files_dict = await get_py_files_dict(repo_path) repo = Repository( - id = uuid5(NAMESPACE_OID, repo_path), - path = repo_path, + id=uuid5(NAMESPACE_OID, repo_path), + path=repo_path, ) yield [repo] - with ProcessPoolExecutor(max_workers = 12) as executor: + with ProcessPoolExecutor(max_workers=12) as executor: loop = asyncio.get_event_loop() tasks = [ @@ -82,7 +86,7 @@ async def get_repo_file_dependencies(repo_path: str) -> AsyncGenerator[list, Non run_coroutine, get_local_script_dependencies, os.path.join(repo_path, file_path), - repo_path + repo_path, ) for file_path, metadata in py_files_dict.items() if metadata.get("source_code") is not None @@ -94,19 +98,24 @@ async def get_repo_file_dependencies(repo_path: str) -> AsyncGenerator[list, Non for (file_path, metadata), dependencies in zip(py_files_dict.items(), results): source_code = metadata.get("source_code") - code_files.append(CodeFile( - id = uuid5(NAMESPACE_OID, file_path), - source_code = source_code, - extracted_id = file_path, - part_of = repo, - depends_on = [ - CodeFile( - id = uuid5(NAMESPACE_OID, dependency), - extracted_id = dependency, - part_of = repo, - source_code = py_files_dict.get(dependency, {}).get("source_code"), - ) for dependency in dependencies - ] if dependencies else None, - )) + code_files.append( + CodeFile( + id=uuid5(NAMESPACE_OID, file_path), + source_code=source_code, + extracted_id=file_path, + part_of=repo, + depends_on=[ + CodeFile( + id=uuid5(NAMESPACE_OID, dependency), + extracted_id=dependency, + part_of=repo, + source_code=py_files_dict.get(dependency, {}).get("source_code"), + ) + for dependency in dependencies + ] + if dependencies + else None, + ) + ) yield code_files diff --git a/cognee/tasks/repo_processor/get_source_code_chunks.py b/cognee/tasks/repo_processor/get_source_code_chunks.py index 4d0ce3200..5e14e11ac 100644 --- a/cognee/tasks/repo_processor/get_source_code_chunks.py +++ b/cognee/tasks/repo_processor/get_source_code_chunks.py @@ -16,7 +16,7 @@ def _count_tokens(tokenizer: tiktoken.Encoding, source_code: str) -> int: def _get_naive_subchunk_token_counts( - tokenizer: tiktoken.Encoding, source_code: str, max_subchunk_tokens: int = 8000 + tokenizer: tiktoken.Encoding, source_code: str, max_subchunk_tokens: int = 8000 ) -> list[tuple[str, int]]: """Splits source code into subchunks of up to max_subchunk_tokens and counts tokens.""" @@ -24,10 +24,10 @@ def _get_naive_subchunk_token_counts( subchunk_token_counts = [] for start_idx in range(0, len(token_ids), max_subchunk_tokens): - subchunk_token_ids = token_ids[start_idx: start_idx + max_subchunk_tokens] + subchunk_token_ids = token_ids[start_idx : start_idx + max_subchunk_tokens] token_count = len(subchunk_token_ids) - subchunk = ''.join( - tokenizer.decode_single_token_bytes(token_id).decode('utf-8', errors='replace') + subchunk = "".join( + tokenizer.decode_single_token_bytes(token_id).decode("utf-8", errors="replace") for token_id in subchunk_token_ids ) subchunk_token_counts.append((subchunk, token_count)) @@ -36,11 +36,11 @@ def _get_naive_subchunk_token_counts( def _get_subchunk_token_counts( - tokenizer: tiktoken.Encoding, - source_code: str, - max_subchunk_tokens: int = 8000, - depth: int = 0, - max_depth: int = 100 + tokenizer: tiktoken.Encoding, + source_code: str, + max_subchunk_tokens: int = 8000, + depth: int = 0, + max_depth: int = 100, ) -> list[tuple[str, int]]: """Splits source code into subchunk and counts tokens for each subchunk.""" if depth > max_depth: @@ -72,24 +72,28 @@ def _get_subchunk_token_counts( subchunk_token_counts.append((subchunk, token_count)) continue - if child.type == 'string': - subchunk_token_counts.extend(_get_naive_subchunk_token_counts(tokenizer, subchunk, max_subchunk_tokens)) + if child.type == "string": + subchunk_token_counts.extend( + _get_naive_subchunk_token_counts(tokenizer, subchunk, max_subchunk_tokens) + ) continue subchunk_token_counts.extend( - _get_subchunk_token_counts(tokenizer, subchunk, max_subchunk_tokens, depth=depth + 1, max_depth=max_depth) + _get_subchunk_token_counts( + tokenizer, subchunk, max_subchunk_tokens, depth=depth + 1, max_depth=max_depth + ) ) return subchunk_token_counts def _get_chunk_source_code( - code_token_counts: list[tuple[str, int]], overlap: float, max_tokens: int + code_token_counts: list[tuple[str, int]], overlap: float, max_tokens: int ) -> tuple[list[tuple[str, int]], str]: """Generates a chunk of source code from tokenized subchunks with overlap handling.""" current_count = 0 cumulative_counts = [] - current_source_code = '' + current_source_code = "" for i, (child_code, token_count) in enumerate(code_token_counts): current_count += token_count @@ -111,11 +115,11 @@ def _get_chunk_source_code( def get_source_code_chunks_from_code_part( - code_file_part: CodePart, - max_tokens: int = 8192, - overlap: float = 0.25, - granularity: float = 0.1, - model_name: str = "text-embedding-3-large" + code_file_part: CodePart, + max_tokens: int = 8192, + overlap: float = 0.25, + granularity: float = 0.1, + model_name: str = "text-embedding-3-large", ) -> Generator[SourceCodeChunk, None, None]: """Yields source code chunks from a CodePart object, with configurable token limits and overlap.""" if not code_file_part.source_code: @@ -124,25 +128,30 @@ def get_source_code_chunks_from_code_part( tokenizer = tiktoken.encoding_for_model(model_name) max_subchunk_tokens = max(1, int(granularity * max_tokens)) - subchunk_token_counts = _get_subchunk_token_counts(tokenizer, code_file_part.source_code, max_subchunk_tokens) + subchunk_token_counts = _get_subchunk_token_counts( + tokenizer, code_file_part.source_code, max_subchunk_tokens + ) previous_chunk = None while subchunk_token_counts: - subchunk_token_counts, chunk_source_code = _get_chunk_source_code(subchunk_token_counts, overlap, max_tokens) + subchunk_token_counts, chunk_source_code = _get_chunk_source_code( + subchunk_token_counts, overlap, max_tokens + ) if not chunk_source_code: continue current_chunk = SourceCodeChunk( id=uuid5(NAMESPACE_OID, chunk_source_code), code_chunk_of=code_file_part, source_code=chunk_source_code, - previous_chunk=previous_chunk + previous_chunk=previous_chunk, ) yield current_chunk previous_chunk = current_chunk -async def get_source_code_chunks(data_points: list[DataPoint], embedding_model="text-embedding-3-large") -> \ - AsyncGenerator[list[DataPoint], None]: +async def get_source_code_chunks( + data_points: list[DataPoint], embedding_model="text-embedding-3-large" +) -> AsyncGenerator[list[DataPoint], None]: """Processes code graph datapoints, create SourceCodeChink datapoints.""" # TODO: Add support for other embedding models, with max_token mapping for data_point in data_points: @@ -156,7 +165,9 @@ async def get_source_code_chunks(data_points: list[DataPoint], embedding_model=" for code_part in data_point.contains: try: yield code_part - for source_code_chunk in get_source_code_chunks_from_code_part(code_part, model_name=embedding_model): + for source_code_chunk in get_source_code_chunks_from_code_part( + code_part, model_name=embedding_model + ): yield source_code_chunk except Exception as e: logger.error(f"Error processing code part: {e}") diff --git a/cognee/tasks/repo_processor/top_down_repo_parse.py b/cognee/tasks/repo_processor/top_down_repo_parse.py index 52f58f811..aed971920 100644 --- a/cognee/tasks/repo_processor/top_down_repo_parse.py +++ b/cognee/tasks/repo_processor/top_down_repo_parse.py @@ -14,6 +14,7 @@ "simple_stmt": "var_def", } + def _create_object_dict(name_node, type_name=None): return { "name": name_node.value, @@ -32,7 +33,11 @@ def _parse_node(node): if node.type == "async_stmt" and len(node.children) > 1: function_node = node.children[1] if function_node.type == "funcdef": - return [_create_object_dict(function_node.name, type_name=_NODE_TYPE_MAP.get(function_node.type))] + return [ + _create_object_dict( + function_node.name, type_name=_NODE_TYPE_MAP.get(function_node.type) + ) + ] if node.type == "simple_stmt": # TODO: Handle multi-level/nested unpacking variable definitions in the future expr_child = node.children[0] @@ -50,7 +55,6 @@ def _parse_node(node): return [] - def extract_importable_objects_with_positions_from_source_code(source_code): """Extract top-level objects in a Python source code string with their positions (line/column).""" try: @@ -82,14 +86,12 @@ def extract_importable_objects_with_positions(file_path): return extract_importable_objects_with_positions_from_source_code(source_code) - def find_entity_usages(script, line, column): """ Return a list of files in the repo where the entity at module_path:line,column is used. """ usages = set() - try: inferred = script.infer(line, column) except Exception as e: @@ -103,9 +105,13 @@ def find_entity_usages(script, line, column): logger.debug(f"Inferred entity: {inferred[0].name}, type: {inferred[0].type}") try: - references = script.get_references(line=line, column=column, scope="project", include_builtins=False) + references = script.get_references( + line=line, column=column, scope="project", include_builtins=False + ) except Exception as e: - logger.error(f"Error retrieving references for entity at {script.path}:{line},{column}: {e}") + logger.error( + f"Error retrieving references for entity at {script.path}:{line},{column}: {e}" + ) references = [] for ref in references: @@ -115,6 +121,7 @@ def find_entity_usages(script, line, column): return list(usages) + def parse_file_with_references(project, file_path): """Parse a file to extract object names and their references within a project.""" try: @@ -152,7 +159,7 @@ def parse_repo(repo_path): logger.error(f"Error creating Jedi project for repository at {repo_path}: {e}") return {} - EXCLUDE_DIRS = {'venv', '.git', '__pycache__', 'build'} + EXCLUDE_DIRS = {"venv", ".git", "__pycache__", "build"} python_files = [ os.path.join(directory, file) @@ -168,4 +175,3 @@ def parse_repo(repo_path): } return results - diff --git a/cognee/tasks/storage/add_data_points.py b/cognee/tasks/storage/add_data_points.py index 47cae4309..21cc5a3c2 100644 --- a/cognee/tasks/storage/add_data_points.py +++ b/cognee/tasks/storage/add_data_points.py @@ -5,7 +5,7 @@ from .index_data_points import index_data_points -async def add_data_points(data_points: list[DataPoint], only_root = False): +async def add_data_points(data_points: list[DataPoint], only_root=False): nodes = [] edges = [] @@ -13,15 +13,18 @@ async def add_data_points(data_points: list[DataPoint], only_root = False): added_edges = {} visited_properties = {} - results = await asyncio.gather(*[ - get_graph_from_model( - data_point, - added_nodes = added_nodes, - added_edges = added_edges, - visited_properties = visited_properties, - only_root = only_root, - ) for data_point in data_points - ]) + results = await asyncio.gather( + *[ + get_graph_from_model( + data_point, + added_nodes=added_nodes, + added_edges=added_edges, + visited_properties=visited_properties, + only_root=only_root, + ) + for data_point in data_points + ] + ) for result_nodes, result_edges in results: nodes.extend(result_nodes) diff --git a/cognee/tasks/storage/index_data_points.py b/cognee/tasks/storage/index_data_points.py index 12af2d2ef..65f516bac 100644 --- a/cognee/tasks/storage/index_data_points.py +++ b/cognee/tasks/storage/index_data_points.py @@ -6,6 +6,7 @@ logger = logging.getLogger("index_data_points") + async def index_data_points(data_points: list[DataPoint]): created_indexes = {} index_points = {} @@ -41,7 +42,10 @@ async def index_data_points(data_points: list[DataPoint]): return data_points -async def get_data_points_from_model(data_point: DataPoint, added_data_points = None, visited_properties = None) -> list[DataPoint]: + +async def get_data_points_from_model( + data_point: DataPoint, added_data_points=None, visited_properties=None +) -> list[DataPoint]: data_points = [] added_data_points = added_data_points or {} visited_properties = visited_properties or {} @@ -55,14 +59,20 @@ async def get_data_points_from_model(data_point: DataPoint, added_data_points = visited_properties[property_key] = True - new_data_points = await get_data_points_from_model(field_value, added_data_points, visited_properties) + new_data_points = await get_data_points_from_model( + field_value, added_data_points, visited_properties + ) for new_point in new_data_points: if str(new_point.id) not in added_data_points: added_data_points[str(new_point.id)] = True data_points.append(new_point) - if isinstance(field_value, list) and len(field_value) > 0 and isinstance(field_value[0], DataPoint): + if ( + isinstance(field_value, list) + and len(field_value) > 0 + and isinstance(field_value[0], DataPoint) + ): for field_value_item in field_value: property_key = f"{str(data_point.id)}{field_name}{str(field_value_item.id)}" @@ -70,43 +80,39 @@ async def get_data_points_from_model(data_point: DataPoint, added_data_points = return [] visited_properties[property_key] = True - - new_data_points = await get_data_points_from_model(field_value_item, added_data_points, visited_properties) + + new_data_points = await get_data_points_from_model( + field_value_item, added_data_points, visited_properties + ) for new_point in new_data_points: if str(new_point.id) not in added_data_points: added_data_points[str(new_point.id)] = True data_points.append(new_point) - if (str(data_point.id) not in added_data_points): + if str(data_point.id) not in added_data_points: data_points.append(data_point) return data_points if __name__ == "__main__": + class Car(DataPoint): model: str color: str - _metadata = { - "index_fields": ["name"], - "type": "Car" - } + _metadata = {"index_fields": ["name"], "type": "Car"} - class Person(DataPoint): name: str age: int owns_car: list[Car] - _metadata = { - "index_fields": ["name"], - "type": "Person" - } + _metadata = {"index_fields": ["name"], "type": "Person"} - car1 = Car(model = "Tesla Model S", color = "Blue") - car2 = Car(model = "Toyota Camry", color = "Red") - person = Person(name = "John", age = 30, owns_car = [car1, car2]) + car1 = Car(model="Tesla Model S", color="Blue") + car2 = Car(model="Toyota Camry", color="Red") + person = Person(name="John", age=30, owns_car=[car1, car2]) data_points = get_data_points_from_model(person) - print(data_points) \ No newline at end of file + print(data_points) diff --git a/cognee/tasks/storage/index_graph_edges.py b/cognee/tasks/storage/index_graph_edges.py index 88514af48..2aeb2bef2 100644 --- a/cognee/tasks/storage/index_graph_edges.py +++ b/cognee/tasks/storage/index_graph_edges.py @@ -8,25 +8,25 @@ async def index_graph_edges(): """ - Indexes graph edges by creating and managing vector indexes for relationship types. + Indexes graph edges by creating and managing vector indexes for relationship types. - This function retrieves edge data from the graph engine, counts distinct relationship - types, and creates `EdgeType` pydantic objects. It ensures that vector indexes are created for - the `relationship_name` field. + This function retrieves edge data from the graph engine, counts distinct relationship + types, and creates `EdgeType` pydantic objects. It ensures that vector indexes are created for + the `relationship_name` field. - Steps: - 1. Initialize the vector engine and graph engine. - 2. Retrieve graph edge data and count relationship types (`relationship_name`). - 3. Create vector indexes for `relationship_name` if they don't exist. - 4. Transform the counted relationships into `EdgeType` objects. - 5. Index the transformed data points in the vector engine. + Steps: + 1. Initialize the vector engine and graph engine. + 2. Retrieve graph edge data and count relationship types (`relationship_name`). + 3. Create vector indexes for `relationship_name` if they don't exist. + 4. Transform the counted relationships into `EdgeType` objects. + 5. Index the transformed data points in the vector engine. - Raises: - RuntimeError: If initialization of the vector engine or graph engine fails. + Raises: + RuntimeError: If initialization of the vector engine or graph engine fails. - Returns: - None - """ + Returns: + None + """ try: created_indexes = {} index_points = {} @@ -40,9 +40,10 @@ async def index_graph_edges(): _, edges_data = await graph_engine.get_graph_data() edge_types = Counter( - item.get('relationship_name') + item.get("relationship_name") for edge in edges_data - for item in edge if isinstance(item, dict) and 'relationship_name' in item + for item in edge + if isinstance(item, dict) and "relationship_name" in item ) for text, count in edge_types.items(): @@ -67,4 +68,4 @@ async def index_graph_edges(): index_name, field_name = index_name.split(".") await vector_engine.index_data_points(index_name, field_name, indexable_points) - return None \ No newline at end of file + return None diff --git a/cognee/tasks/summarization/mock_summary.py b/cognee/tasks/summarization/mock_summary.py index e61385bb1..f60ce2d82 100644 --- a/cognee/tasks/summarization/mock_summary.py +++ b/cognee/tasks/summarization/mock_summary.py @@ -1,5 +1,6 @@ from cognee.shared.data_models import SummarizedCode, SummarizedClass, SummarizedFunction + def get_mock_summarized_code() -> SummarizedCode: return SummarizedCode( file_name="mock_file.py", @@ -34,4 +35,4 @@ def get_mock_summarized_code() -> SummarizedCode: ) ], workflow_description="This is a mock workflow description.", - ) \ No newline at end of file + ) diff --git a/cognee/tasks/summarization/models.py b/cognee/tasks/summarization/models.py index 5b0345015..fc62209ce 100644 --- a/cognee/tasks/summarization/models.py +++ b/cognee/tasks/summarization/models.py @@ -10,10 +10,7 @@ class TextSummary(DataPoint): text: str made_from: DocumentChunk - _metadata: dict = { - "index_fields": ["text"], - "type": "TextSummary" - } + _metadata: dict = {"index_fields": ["text"], "type": "TextSummary"} class CodeSummary(DataPoint): @@ -21,7 +18,4 @@ class CodeSummary(DataPoint): text: str summarizes: Union[CodeFile, CodePart, SourceCodeChunk] - _metadata: dict = { - "index_fields": ["text"], - "type": "CodeSummary" - } + _metadata: dict = {"index_fields": ["text"], "type": "CodeSummary"} diff --git a/cognee/tasks/summarization/query_summaries.py b/cognee/tasks/summarization/query_summaries.py index d9ec0fa00..5f04e3eb4 100644 --- a/cognee/tasks/summarization/query_summaries.py +++ b/cognee/tasks/summarization/query_summaries.py @@ -1,5 +1,6 @@ from cognee.infrastructure.databases.vector import get_vector_engine + async def query_summaries(query: str) -> list: """ Parameters: @@ -10,7 +11,7 @@ async def query_summaries(query: str) -> list: """ vector_engine = get_vector_engine() - summaries_results = await vector_engine.search("text_summary_text", query, limit = 5) + summaries_results = await vector_engine.search("text_summary_text", query, limit=5) summaries = [summary.payload for summary in summaries_results] diff --git a/cognee/tasks/summarization/summarize_text.py b/cognee/tasks/summarization/summarize_text.py index 60ec66f5c..df7ac6740 100644 --- a/cognee/tasks/summarization/summarize_text.py +++ b/cognee/tasks/summarization/summarize_text.py @@ -6,6 +6,7 @@ from cognee.modules.chunking.models.DocumentChunk import DocumentChunk from .models import TextSummary + async def summarize_text(data_chunks: list[DocumentChunk], summarization_model: Type[BaseModel]): if len(data_chunks) == 0: return data_chunks @@ -16,10 +17,11 @@ async def summarize_text(data_chunks: list[DocumentChunk], summarization_model: summaries = [ TextSummary( - id = uuid5(chunk.id, "TextSummary"), - made_from = chunk, - text = chunk_summaries[chunk_index].summary, - ) for (chunk_index, chunk) in enumerate(data_chunks) + id=uuid5(chunk.id, "TextSummary"), + made_from=chunk, + text=chunk_summaries[chunk_index].summary, + ) + for (chunk_index, chunk) in enumerate(data_chunks) ] return summaries diff --git a/cognee/tasks/temporal_awareness/__init__.py b/cognee/tasks/temporal_awareness/__init__.py index 9b6717f5f..4a47d33be 100644 --- a/cognee/tasks/temporal_awareness/__init__.py +++ b/cognee/tasks/temporal_awareness/__init__.py @@ -1,4 +1,2 @@ -from .build_graph_with_temporal_awareness import \ - build_graph_with_temporal_awareness -from .search_graph_with_temporal_awareness import \ - search_graph_with_temporal_awareness +from .build_graph_with_temporal_awareness import build_graph_with_temporal_awareness +from .search_graph_with_temporal_awareness import search_graph_with_temporal_awareness diff --git a/cognee/tasks/temporal_awareness/build_graph_with_temporal_awareness.py b/cognee/tasks/temporal_awareness/build_graph_with_temporal_awareness.py index ac534623b..ecbf2b6be 100644 --- a/cognee/tasks/temporal_awareness/build_graph_with_temporal_awareness.py +++ b/cognee/tasks/temporal_awareness/build_graph_with_temporal_awareness.py @@ -6,12 +6,11 @@ async def build_graph_with_temporal_awareness(text_list): - url = os.getenv("GRAPH_DATABASE_URL") password = os.getenv("GRAPH_DATABASE_PASSWORD") graphiti = Graphiti(url, "neo4j", password) - - await graphiti.build_indices_and_constraints() + + await graphiti.build_indices_and_constraints() print("Graph database initialized.") for i, text in enumerate(text_list): @@ -20,7 +19,7 @@ async def build_graph_with_temporal_awareness(text_list): episode_body=text, source=EpisodeType.text, source_description="input", - reference_time=datetime.now() + reference_time=datetime.now(), ) print(f"Added text: {text[:35]}...") - return graphiti \ No newline at end of file + return graphiti diff --git a/cognee/tasks/temporal_awareness/search_graph_with_temporal_awareness.py b/cognee/tasks/temporal_awareness/search_graph_with_temporal_awareness.py index 8e2cd0fc8..247b04e71 100644 --- a/cognee/tasks/temporal_awareness/search_graph_with_temporal_awareness.py +++ b/cognee/tasks/temporal_awareness/search_graph_with_temporal_awareness.py @@ -1,6 +1,4 @@ - - async def search_graph_with_temporal_awareness(graphiti, query): search_result = await graphiti.search(query) await graphiti.close() - return search_result \ No newline at end of file + return search_result diff --git a/cognee/tests/get_graph_url.py b/cognee/tests/get_graph_url.py index c5909676d..1e0abbaf0 100644 --- a/cognee/tests/get_graph_url.py +++ b/cognee/tests/get_graph_url.py @@ -3,14 +3,23 @@ from cognee.infrastructure.databases.graph import get_graph_engine if __name__ == "__main__": + async def main(): import os import pathlib import cognee - data_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_library")).resolve()) + data_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_library") + ).resolve() + ) cognee.config.data_root_directory(data_directory_path) - cognee_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_library")).resolve()) + cognee_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_library") + ).resolve() + ) cognee.config.system_root_directory(cognee_directory_path) graph_client = await get_graph_engine() diff --git a/cognee/tests/integration/documents/AudioDocument_test.py b/cognee/tests/integration/documents/AudioDocument_test.py index 151f4c0b2..dbd43ddda 100644 --- a/cognee/tests/integration/documents/AudioDocument_test.py +++ b/cognee/tests/integration/documents/AudioDocument_test.py @@ -25,13 +25,16 @@ def test_AudioDocument(): - document = AudioDocument( - id=uuid.uuid4(), name="audio-dummy-test", raw_data_location="", metadata_id=uuid.uuid4(), mime_type="", + id=uuid.uuid4(), + name="audio-dummy-test", + raw_data_location="", + metadata_id=uuid.uuid4(), + mime_type="", ) with patch.object(AudioDocument, "create_transcript", return_value=TEST_TEXT): for ground_truth, paragraph_data in zip( - GROUND_TRUTH, document.read(chunk_size=64, chunker='text_chunker') + GROUND_TRUTH, document.read(chunk_size=64, chunker="text_chunker") ): assert ( ground_truth["word_count"] == paragraph_data.word_count diff --git a/cognee/tests/integration/documents/ImageDocument_test.py b/cognee/tests/integration/documents/ImageDocument_test.py index 40e0155af..c0877ae99 100644 --- a/cognee/tests/integration/documents/ImageDocument_test.py +++ b/cognee/tests/integration/documents/ImageDocument_test.py @@ -14,14 +14,16 @@ def test_ImageDocument(): - document = ImageDocument( - id=uuid.uuid4(), name="image-dummy-test", raw_data_location="", metadata_id=uuid.uuid4(), mime_type="", + id=uuid.uuid4(), + name="image-dummy-test", + raw_data_location="", + metadata_id=uuid.uuid4(), + mime_type="", ) with patch.object(ImageDocument, "transcribe_image", return_value=TEST_TEXT): - for ground_truth, paragraph_data in zip( - GROUND_TRUTH, document.read(chunk_size=64, chunker='text_chunker') + GROUND_TRUTH, document.read(chunk_size=64, chunker="text_chunker") ): assert ( ground_truth["word_count"] == paragraph_data.word_count diff --git a/cognee/tests/integration/documents/PdfDocument_test.py b/cognee/tests/integration/documents/PdfDocument_test.py index 25d4cf6c6..8f28815d3 100644 --- a/cognee/tests/integration/documents/PdfDocument_test.py +++ b/cognee/tests/integration/documents/PdfDocument_test.py @@ -17,12 +17,15 @@ def test_PdfDocument(): "artificial-intelligence.pdf", ) document = PdfDocument( - id=uuid.uuid4(), name="Test document.pdf", raw_data_location=test_file_path, metadata_id=uuid.uuid4(), + id=uuid.uuid4(), + name="Test document.pdf", + raw_data_location=test_file_path, + metadata_id=uuid.uuid4(), mime_type="", ) for ground_truth, paragraph_data in zip( - GROUND_TRUTH, document.read(chunk_size=1024, chunker='text_chunker') + GROUND_TRUTH, document.read(chunk_size=1024, chunker="text_chunker") ): assert ( ground_truth["word_count"] == paragraph_data.word_count diff --git a/cognee/tests/integration/documents/TextDocument_test.py b/cognee/tests/integration/documents/TextDocument_test.py index 91f38968e..1e143d563 100644 --- a/cognee/tests/integration/documents/TextDocument_test.py +++ b/cognee/tests/integration/documents/TextDocument_test.py @@ -29,11 +29,15 @@ def test_TextDocument(input_file, chunk_size): input_file, ) document = TextDocument( - id=uuid.uuid4(), name=input_file, raw_data_location=test_file_path, metadata_id=uuid.uuid4(), mime_type="", + id=uuid.uuid4(), + name=input_file, + raw_data_location=test_file_path, + metadata_id=uuid.uuid4(), + mime_type="", ) for ground_truth, paragraph_data in zip( - GROUND_TRUTH[input_file], document.read(chunk_size=chunk_size, chunker='text_chunker') + GROUND_TRUTH[input_file], document.read(chunk_size=chunk_size, chunker="text_chunker") ): assert ( ground_truth["word_count"] == paragraph_data.word_count diff --git a/cognee/tests/integration/documents/UnstructuredDocument_test.py b/cognee/tests/integration/documents/UnstructuredDocument_test.py index 418b18810..03b8deb49 100644 --- a/cognee/tests/integration/documents/UnstructuredDocument_test.py +++ b/cognee/tests/integration/documents/UnstructuredDocument_test.py @@ -3,6 +3,7 @@ from cognee.modules.data.processing.document_types.UnstructuredDocument import UnstructuredDocument + def test_UnstructuredDocument(): # Define file paths of test data pptx_file_path = os.path.join( @@ -35,46 +36,67 @@ def test_UnstructuredDocument(): # Define test documents pptx_document = UnstructuredDocument( - id=uuid.uuid4(), name="example.pptx", raw_data_location=pptx_file_path, metadata_id=uuid.uuid4(), - mime_type="application/vnd.openxmlformats-officedocument.presentationml.presentation" + id=uuid.uuid4(), + name="example.pptx", + raw_data_location=pptx_file_path, + metadata_id=uuid.uuid4(), + mime_type="application/vnd.openxmlformats-officedocument.presentationml.presentation", ) docx_document = UnstructuredDocument( - id=uuid.uuid4(), name="example.docx", raw_data_location=docx_file_path, metadata_id=uuid.uuid4(), - mime_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document" + id=uuid.uuid4(), + name="example.docx", + raw_data_location=docx_file_path, + metadata_id=uuid.uuid4(), + mime_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document", ) csv_document = UnstructuredDocument( - id=uuid.uuid4(), name="example.csv", raw_data_location=csv_file_path, metadata_id=uuid.uuid4(), - mime_type="text/csv" + id=uuid.uuid4(), + name="example.csv", + raw_data_location=csv_file_path, + metadata_id=uuid.uuid4(), + mime_type="text/csv", ) xlsx_document = UnstructuredDocument( - id=uuid.uuid4(), name="example.xlsx", raw_data_location=xlsx_file_path, metadata_id=uuid.uuid4(), - mime_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + id=uuid.uuid4(), + name="example.xlsx", + raw_data_location=xlsx_file_path, + metadata_id=uuid.uuid4(), + mime_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ) # Test PPTX for paragraph_data in pptx_document.read(chunk_size=1024): - assert 19 == paragraph_data.word_count, f' 19 != {paragraph_data.word_count = }' - assert 104 == len(paragraph_data.text), f' 104 != {len(paragraph_data.text) = }' - assert 'sentence_cut' == paragraph_data.cut_type, f' sentence_cut != {paragraph_data.cut_type = }' + assert 19 == paragraph_data.word_count, f" 19 != {paragraph_data.word_count = }" + assert 104 == len(paragraph_data.text), f" 104 != {len(paragraph_data.text) = }" + assert ( + "sentence_cut" == paragraph_data.cut_type + ), f" sentence_cut != {paragraph_data.cut_type = }" # Test DOCX for paragraph_data in docx_document.read(chunk_size=1024): - assert 16 == paragraph_data.word_count, f' 16 != {paragraph_data.word_count = }' - assert 145 == len(paragraph_data.text), f' 145 != {len(paragraph_data.text) = }' - assert 'sentence_end' == paragraph_data.cut_type, f' sentence_end != {paragraph_data.cut_type = }' + assert 16 == paragraph_data.word_count, f" 16 != {paragraph_data.word_count = }" + assert 145 == len(paragraph_data.text), f" 145 != {len(paragraph_data.text) = }" + assert ( + "sentence_end" == paragraph_data.cut_type + ), f" sentence_end != {paragraph_data.cut_type = }" # TEST CSV for paragraph_data in csv_document.read(chunk_size=1024): - assert 15 == paragraph_data.word_count, f' 15 != {paragraph_data.word_count = }' - assert 'A A A A A A A A A,A A A A A A,A A' == paragraph_data.text, \ - f'Read text doesn\'t match expected text: {paragraph_data.text}' - assert 'sentence_cut' == paragraph_data.cut_type, f' sentence_cut != {paragraph_data.cut_type = }' + assert 15 == paragraph_data.word_count, f" 15 != {paragraph_data.word_count = }" + assert ( + "A A A A A A A A A,A A A A A A,A A" == paragraph_data.text + ), f"Read text doesn't match expected text: {paragraph_data.text}" + assert ( + "sentence_cut" == paragraph_data.cut_type + ), f" sentence_cut != {paragraph_data.cut_type = }" # Test XLSX for paragraph_data in xlsx_document.read(chunk_size=1024): - assert 36 == paragraph_data.word_count, f' 36 != {paragraph_data.word_count = }' - assert 171 == len(paragraph_data.text), f' 171 != {len(paragraph_data.text) = }' - assert 'sentence_cut' == paragraph_data.cut_type, f' sentence_cut != {paragraph_data.cut_type = }' + assert 36 == paragraph_data.word_count, f" 36 != {paragraph_data.word_count = }" + assert 171 == len(paragraph_data.text), f" 171 != {len(paragraph_data.text) = }" + assert ( + "sentence_cut" == paragraph_data.cut_type + ), f" sentence_cut != {paragraph_data.cut_type = }" diff --git a/cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py b/cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py index e57b16f39..6a4024342 100644 --- a/cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py +++ b/cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py @@ -31,9 +31,7 @@ async def multiply_by_two(num): results = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] index = 0 async for result in tasks_run: - assert ( - result == results[index] - ), f"at {index = }: {result = } != {results[index] = }" + assert result == results[index], f"at {index = }: {result = } != {results[index] = }" index += 1 diff --git a/cognee/tests/integration/run_toy_tasks/run_tasks_test.py b/cognee/tests/integration/run_toy_tasks/run_tasks_test.py index d0a2af80b..54613c214 100644 --- a/cognee/tests/integration/run_toy_tasks/run_tasks_test.py +++ b/cognee/tests/integration/run_toy_tasks/run_tasks_test.py @@ -33,9 +33,7 @@ async def add_one_single(num): results = [5, 7, 9, 11, 13, 15, 17, 19, 21, 23] index = 0 async for result in pipeline: - assert ( - result == results[index] - ), f"at {index = }: {result = } != {results[index] = }" + assert result == results[index], f"at {index = }: {result = } != {results[index] = }" index += 1 diff --git a/cognee/tests/test_deduplication.py b/cognee/tests/test_deduplication.py index 467a52368..9c2df032d 100644 --- a/cognee/tests/test_deduplication.py +++ b/cognee/tests/test_deduplication.py @@ -8,6 +8,7 @@ logging.basicConfig(level=logging.DEBUG) + async def test_deduplication(): await cognee.prune.prune_data() await cognee.prune.prune_system(metadata=True) @@ -29,7 +30,9 @@ async def test_deduplication(): result = await relational_engine.get_all_data_from_table("data") assert len(result) == 1, "More than one data entity was found." - assert result[0]["name"] == "Natural_language_processing_copy", "Result name does not match expected value." + assert ( + result[0]["name"] == "Natural_language_processing_copy" + ), "Result name does not match expected value." result = await relational_engine.get_all_data_from_table("datasets") assert len(result) == 2, "Unexpected number of datasets found." @@ -58,15 +61,15 @@ async def test_deduplication(): result = await relational_engine.get_all_data_from_table("data") assert len(result) == 1, "More than one data entity was found." - assert hashlib.md5(text.encode('utf-8')).hexdigest() in result[0]["name"], "Content hash is not a part of file name." + assert ( + hashlib.md5(text.encode("utf-8")).hexdigest() in result[0]["name"] + ), "Content hash is not a part of file name." await cognee.prune.prune_data() await cognee.prune.prune_system(metadata=True) # Test deduplication of image files - explanation_file_path = os.path.join( - pathlib.Path(__file__).parent, "test_data/example.png" - ) + explanation_file_path = os.path.join(pathlib.Path(__file__).parent, "test_data/example.png") explanation_file_path2 = os.path.join( pathlib.Path(__file__).parent, "test_data/example_copy.png" ) @@ -100,11 +103,7 @@ async def test_deduplication(): async def test_deduplication_postgres(): cognee.config.set_vector_db_config( - { - "vector_db_url": "", - "vector_db_key": "", - "vector_db_provider": "pgvector" - } + {"vector_db_url": "", "vector_db_key": "", "vector_db_provider": "pgvector"} ) cognee.config.set_relational_db_config( { @@ -119,13 +118,10 @@ async def test_deduplication_postgres(): await test_deduplication() + async def test_deduplication_sqlite(): cognee.config.set_vector_db_config( - { - "vector_db_url": "", - "vector_db_key": "", - "vector_db_provider": "lancedb" - } + {"vector_db_url": "", "vector_db_key": "", "vector_db_provider": "lancedb"} ) cognee.config.set_relational_db_config( { @@ -137,7 +133,6 @@ async def test_deduplication_sqlite(): async def main(): - data_directory_path = str( pathlib.Path( os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_deduplication") @@ -154,6 +149,7 @@ async def main(): await test_deduplication_postgres() await test_deduplication_sqlite() + if __name__ == "__main__": import asyncio diff --git a/cognee/tests/test_falkordb.py b/cognee/tests/test_falkordb.py index 25fe81a75..07ece9eb2 100755 --- a/cognee/tests/test_falkordb.py +++ b/cognee/tests/test_falkordb.py @@ -5,20 +5,31 @@ from cognee.api.v1.search import SearchType from cognee.shared.utils import render_graph -logging.basicConfig(level = logging.DEBUG) +logging.basicConfig(level=logging.DEBUG) -async def main(): - data_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_falkordb")).resolve()) + +async def main(): + data_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_falkordb") + ).resolve() + ) cognee.config.data_root_directory(data_directory_path) - cognee_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_falkordb")).resolve()) + cognee_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_falkordb") + ).resolve() + ) cognee.config.system_root_directory(cognee_directory_path) await cognee.prune.prune_data() - await cognee.prune.prune_system(metadata = True) + await cognee.prune.prune_system(metadata=True) dataset_name = "artificial_intelligence" - ai_text_file_path = os.path.join(pathlib.Path(__file__).parent, "test_data/artificial-intelligence.pdf") + ai_text_file_path = os.path.join( + pathlib.Path(__file__).parent, "test_data/artificial-intelligence.pdf" + ) await cognee.add([ai_text_file_path], dataset_name) text = """A large language model (LLM) is a language model notable for its ability to achieve general-purpose language generation and other natural language processing tasks such as classification. LLMs acquire these abilities by learning statistical relationships from text documents during a computationally intensive self-supervised and semi-supervised training process. LLMs can be used for text generation, a form of generative AI, by taking an input text and repeatedly predicting the next token or word. @@ -34,23 +45,24 @@ async def main(): # await render_graph(None, include_labels = True, include_nodes = True) from cognee.infrastructure.databases.vector import get_vector_engine + vector_engine = get_vector_engine() random_node = (await vector_engine.search("entity.name", "AI"))[0] random_node_name = random_node.payload["text"] - search_results = await cognee.search(SearchType.INSIGHTS, query_text = random_node_name) + search_results = await cognee.search(SearchType.INSIGHTS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted sentences are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.CHUNKS, query_text = random_node_name) + search_results = await cognee.search(SearchType.CHUNKS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted chunks are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.SUMMARIES, query_text = random_node_name) + search_results = await cognee.search(SearchType.SUMMARIES, query_text=random_node_name) assert len(search_results) != 0, "Query related summaries don't exist." print("\nExtracted summaries are:\n") for result in search_results: @@ -72,12 +84,18 @@ async def main(): assert len(collection_names) == 0, "LanceDB vector database is not empty" from cognee.infrastructure.databases.relational import get_relational_engine - assert not os.path.exists(get_relational_engine().db_path), "SQLite relational database is not empty" + + assert not os.path.exists( + get_relational_engine().db_path + ), "SQLite relational database is not empty" from cognee.infrastructure.databases.graph import get_graph_config + graph_config = get_graph_config() assert not os.path.exists(graph_config.graph_file_path), "Networkx graph database is not empty" + if __name__ == "__main__": import asyncio + asyncio.run(main(), debug=True) diff --git a/cognee/tests/test_library.py b/cognee/tests/test_library.py index 6c9d41800..8352b4161 100755 --- a/cognee/tests/test_library.py +++ b/cognee/tests/test_library.py @@ -4,20 +4,31 @@ import cognee from cognee.api.v1.search import SearchType -logging.basicConfig(level = logging.DEBUG) +logging.basicConfig(level=logging.DEBUG) -async def main(): - data_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_library")).resolve()) + +async def main(): + data_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_library") + ).resolve() + ) cognee.config.data_root_directory(data_directory_path) - cognee_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_library")).resolve()) + cognee_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_library") + ).resolve() + ) cognee.config.system_root_directory(cognee_directory_path) await cognee.prune.prune_data() - await cognee.prune.prune_system(metadata = True) + await cognee.prune.prune_system(metadata=True) dataset_name = "artificial_intelligence" - ai_text_file_path = os.path.join(pathlib.Path(__file__).parent, "test_data/artificial-intelligence.pdf") + ai_text_file_path = os.path.join( + pathlib.Path(__file__).parent, "test_data/artificial-intelligence.pdf" + ) await cognee.add([ai_text_file_path], dataset_name) text = """A large language model (LLM) is a language model notable for its ability to achieve general-purpose language generation and other natural language processing tasks such as classification. LLMs acquire these abilities by learning statistical relationships from text documents during a computationally intensive self-supervised and semi-supervised training process. LLMs can be used for text generation, a form of generative AI, by taking an input text and repeatedly predicting the next token or word. @@ -31,23 +42,24 @@ async def main(): await cognee.cognify([dataset_name]) from cognee.infrastructure.databases.vector import get_vector_engine + vector_engine = get_vector_engine() random_node = (await vector_engine.search("entity_name", "AI"))[0] random_node_name = random_node.payload["text"] - search_results = await cognee.search(SearchType.INSIGHTS, query_text = random_node_name) + search_results = await cognee.search(SearchType.INSIGHTS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted sentences are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.CHUNKS, query_text = random_node_name) + search_results = await cognee.search(SearchType.CHUNKS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted chunks are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.SUMMARIES, query_text = random_node_name) + search_results = await cognee.search(SearchType.SUMMARIES, query_text=random_node_name) assert len(search_results) != 0, "Query related summaries don't exist." print("\nExtracted summaries are:\n") for result in search_results: @@ -69,12 +81,18 @@ async def main(): assert len(collection_names) == 0, "LanceDB vector database is not empty" from cognee.infrastructure.databases.relational import get_relational_engine - assert not os.path.exists(get_relational_engine().db_path), "SQLite relational database is not empty" + + assert not os.path.exists( + get_relational_engine().db_path + ), "SQLite relational database is not empty" from cognee.infrastructure.databases.graph import get_graph_config + graph_config = get_graph_config() assert not os.path.exists(graph_config.graph_file_path), "Networkx graph database is not empty" + if __name__ == "__main__": import asyncio + asyncio.run(main(), debug=True) diff --git a/cognee/tests/test_milvus.py b/cognee/tests/test_milvus.py index b32d3590b..da02ca936 100644 --- a/cognee/tests/test_milvus.py +++ b/cognee/tests/test_milvus.py @@ -10,17 +10,23 @@ async def main(): cognee.config.set_vector_db_provider("milvus") data_directory_path = str( - pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_milvus")).resolve()) + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_milvus") + ).resolve() + ) cognee.config.data_root_directory(data_directory_path) cognee_directory_path = str( - pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_milvus")).resolve()) + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_milvus") + ).resolve() + ) cognee.config.system_root_directory(cognee_directory_path) cognee.config.set_vector_db_config( { "vector_db_url": os.path.join(cognee_directory_path, "databases/milvus.db"), "vector_db_key": "", - "vector_db_provider": "milvus" + "vector_db_provider": "milvus", } ) @@ -29,7 +35,9 @@ async def main(): dataset_name = "cs_explanations" - explanation_file_path = os.path.join(pathlib.Path(__file__).parent, "test_data/Natural_language_processing.txt") + explanation_file_path = os.path.join( + pathlib.Path(__file__).parent, "test_data/Natural_language_processing.txt" + ) await cognee.add([explanation_file_path], dataset_name) text = """A quantum computer is a computer that takes advantage of quantum mechanical phenomena. @@ -45,6 +53,7 @@ async def main(): await cognee.cognify([dataset_name]) from cognee.infrastructure.databases.vector import get_vector_engine + vector_engine = get_vector_engine() random_node = (await vector_engine.search("entity_name", "Quantum computer"))[0] random_node_name = random_node.payload["text"] @@ -81,4 +90,5 @@ async def main(): if __name__ == "__main__": import asyncio + asyncio.run(main()) diff --git a/cognee/tests/test_neo4j.py b/cognee/tests/test_neo4j.py index 92e5b5f05..07274c010 100644 --- a/cognee/tests/test_neo4j.py +++ b/cognee/tests/test_neo4j.py @@ -1,4 +1,3 @@ - import os import logging import pathlib @@ -8,19 +7,30 @@ logging.basicConfig(level=logging.DEBUG) + async def main(): cognee.config.set_graph_database_provider("neo4j") - data_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_neo4j")).resolve()) + data_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_neo4j") + ).resolve() + ) cognee.config.data_root_directory(data_directory_path) - cognee_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_neo4j")).resolve()) + cognee_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_neo4j") + ).resolve() + ) cognee.config.system_root_directory(cognee_directory_path) await cognee.prune.prune_data() - await cognee.prune.prune_system(metadata = True) + await cognee.prune.prune_system(metadata=True) dataset_name = "cs_explanations" - explanation_file_path = os.path.join(pathlib.Path(__file__).parent, "test_data/Natural_language_processing.txt") + explanation_file_path = os.path.join( + pathlib.Path(__file__).parent, "test_data/Natural_language_processing.txt" + ) await cognee.add([explanation_file_path], dataset_name) text = """A quantum computer is a computer that takes advantage of quantum mechanical phenomena. @@ -36,23 +46,24 @@ async def main(): await cognee.cognify([dataset_name]) from cognee.infrastructure.databases.vector import get_vector_engine + vector_engine = get_vector_engine() random_node = (await vector_engine.search("entity_name", "Quantum computer"))[0] random_node_name = random_node.payload["text"] - search_results = await cognee.search(SearchType.INSIGHTS, query_text = random_node_name) + search_results = await cognee.search(SearchType.INSIGHTS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted sentences are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.CHUNKS, query_text = random_node_name) + search_results = await cognee.search(SearchType.CHUNKS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted chunks are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.SUMMARIES, query_text = random_node_name) + search_results = await cognee.search(SearchType.SUMMARIES, query_text=random_node_name) assert len(search_results) != 0, "Query related summaries don't exist." print("\nExtracted summaries are:\n") for result in search_results: @@ -62,7 +73,7 @@ async def main(): assert len(history) == 6, "Search history is not correct." - results = await brute_force_triplet_search('What is a quantum computer?') + results = await brute_force_triplet_search("What is a quantum computer?") assert len(results) > 0 await cognee.prune.prune_data() @@ -70,10 +81,13 @@ async def main(): await cognee.prune.prune_system(metadata=True) from cognee.infrastructure.databases.graph import get_graph_engine + graph_engine = await get_graph_engine() nodes, edges = await graph_engine.get_graph_data() assert len(nodes) == 0 and len(edges) == 0, "Neo4j graph database is not empty" + if __name__ == "__main__": import asyncio + asyncio.run(main()) diff --git a/cognee/tests/test_pgvector.py b/cognee/tests/test_pgvector.py index 417904089..c241177f0 100644 --- a/cognee/tests/test_pgvector.py +++ b/cognee/tests/test_pgvector.py @@ -10,6 +10,7 @@ logging.basicConfig(level=logging.DEBUG) + async def test_local_file_deletion(data_text, file_location): from sqlalchemy import select import hashlib @@ -23,40 +24,51 @@ async def test_local_file_deletion(data_text, file_location): data_hash = hashlib.md5(encoded_text).hexdigest() # Get data entry from database based on hash contents data = (await session.scalars(select(Data).where(Data.content_hash == data_hash))).one() - assert os.path.isfile(data.raw_data_location), f"Data location doesn't exist: {data.raw_data_location}" + assert os.path.isfile( + data.raw_data_location + ), f"Data location doesn't exist: {data.raw_data_location}" # Test deletion of data along with local files created by cognee await engine.delete_data_entity(data.id) assert not os.path.exists( - data.raw_data_location), f"Data location still exists after deletion: {data.raw_data_location}" + data.raw_data_location + ), f"Data location still exists after deletion: {data.raw_data_location}" async with engine.get_async_session() as session: # Get data entry from database based on file path - data = (await session.scalars(select(Data).where(Data.raw_data_location == file_location))).one() - assert os.path.isfile(data.raw_data_location), f"Data location doesn't exist: {data.raw_data_location}" + data = ( + await session.scalars(select(Data).where(Data.raw_data_location == file_location)) + ).one() + assert os.path.isfile( + data.raw_data_location + ), f"Data location doesn't exist: {data.raw_data_location}" # Test local files not created by cognee won't get deleted await engine.delete_data_entity(data.id) - assert os.path.exists(data.raw_data_location), f"Data location doesn't exists: {data.raw_data_location}" + assert os.path.exists( + data.raw_data_location + ), f"Data location doesn't exists: {data.raw_data_location}" + async def test_getting_of_documents(dataset_name_1): # Test getting of documents for search per dataset from cognee.modules.users.permissions.methods import get_document_ids_for_user + user = await get_default_user() document_ids = await get_document_ids_for_user(user.id, [dataset_name_1]) - assert len(document_ids) == 1, f"Number of expected documents doesn't match {len(document_ids)} != 1" + assert ( + len(document_ids) == 1 + ), f"Number of expected documents doesn't match {len(document_ids)} != 1" # Test getting of documents for search when no dataset is provided user = await get_default_user() document_ids = await get_document_ids_for_user(user.id) - assert len(document_ids) == 2, f"Number of expected documents doesn't match {len(document_ids)} != 2" + assert ( + len(document_ids) == 2 + ), f"Number of expected documents doesn't match {len(document_ids)} != 2" async def main(): cognee.config.set_vector_db_config( - { - "vector_db_url": "", - "vector_db_key": "", - "vector_db_provider": "pgvector" - } + {"vector_db_url": "", "vector_db_key": "", "vector_db_provider": "pgvector"} ) cognee.config.set_relational_db_config( { @@ -84,7 +96,7 @@ async def main(): cognee.config.system_root_directory(cognee_directory_path) await cognee.prune.prune_data() - await cognee.prune.prune_system(metadata = True) + await cognee.prune.prune_system(metadata=True) dataset_name_1 = "natural_language" dataset_name_2 = "quantum" @@ -114,19 +126,21 @@ async def main(): random_node = (await vector_engine.search("entity_name", "Quantum computer"))[0] random_node_name = random_node.payload["text"] - search_results = await cognee.search(SearchType.INSIGHTS, query_text = random_node_name) + search_results = await cognee.search(SearchType.INSIGHTS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted sentences are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.CHUNKS, query_text = random_node_name, datasets=[dataset_name_2]) + search_results = await cognee.search( + SearchType.CHUNKS, query_text=random_node_name, datasets=[dataset_name_2] + ) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted chunks are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.SUMMARIES, query_text = random_node_name) + search_results = await cognee.search(SearchType.SUMMARIES, query_text=random_node_name) assert len(search_results) != 0, "Query related summaries don't exist." print("\n\nExtracted summaries are:\n") for result in search_results: @@ -135,7 +149,7 @@ async def main(): history = await cognee.get_search_history() assert len(history) == 6, "Search history is not correct." - results = await brute_force_triplet_search('What is a quantum computer?') + results = await brute_force_triplet_search("What is a quantum computer?") assert len(results) > 0 await test_local_file_deletion(text, explanation_file_path) @@ -147,6 +161,7 @@ async def main(): tables_in_database = await vector_engine.get_table_names() assert len(tables_in_database) == 0, "PostgreSQL database is not empty" + if __name__ == "__main__": import asyncio diff --git a/cognee/tests/test_qdrant.py b/cognee/tests/test_qdrant.py index f32e0b4a4..7f82a569f 100644 --- a/cognee/tests/test_qdrant.py +++ b/cognee/tests/test_qdrant.py @@ -1,5 +1,3 @@ - - import os import logging import pathlib @@ -9,19 +7,30 @@ logging.basicConfig(level=logging.DEBUG) + async def main(): cognee.config.set_vector_db_provider("qdrant") - data_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_qdrant")).resolve()) + data_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_qdrant") + ).resolve() + ) cognee.config.data_root_directory(data_directory_path) - cognee_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_qdrant")).resolve()) + cognee_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_qdrant") + ).resolve() + ) cognee.config.system_root_directory(cognee_directory_path) await cognee.prune.prune_data() - await cognee.prune.prune_system(metadata = True) + await cognee.prune.prune_system(metadata=True) dataset_name = "cs_explanations" - explanation_file_path = os.path.join(pathlib.Path(__file__).parent, "test_data/Natural_language_processing.txt") + explanation_file_path = os.path.join( + pathlib.Path(__file__).parent, "test_data/Natural_language_processing.txt" + ) await cognee.add([explanation_file_path], dataset_name) text = """A quantum computer is a computer that takes advantage of quantum mechanical phenomena. @@ -37,23 +46,24 @@ async def main(): await cognee.cognify([dataset_name]) from cognee.infrastructure.databases.vector import get_vector_engine + vector_engine = get_vector_engine() random_node = (await vector_engine.search("entity_name", "Quantum computer"))[0] random_node_name = random_node.payload["text"] - search_results = await cognee.search(SearchType.INSIGHTS, query_text = random_node_name) + search_results = await cognee.search(SearchType.INSIGHTS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted sentences are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.CHUNKS, query_text = random_node_name) + search_results = await cognee.search(SearchType.CHUNKS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted chunks are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.SUMMARIES, query_text = random_node_name) + search_results = await cognee.search(SearchType.SUMMARIES, query_text=random_node_name) assert len(search_results) != 0, "Query related summaries don't exist." print("\nExtracted summaries are:\n") for result in search_results: @@ -62,7 +72,7 @@ async def main(): history = await cognee.get_search_history() assert len(history) == 6, "Search history is not correct." - results = await brute_force_triplet_search('What is a quantum computer?') + results = await brute_force_triplet_search("What is a quantum computer?") assert len(results) > 0 await cognee.prune.prune_data() @@ -73,6 +83,8 @@ async def main(): collections_response = await qdrant_client.get_collections() assert len(collections_response.collections) == 0, "QDrant vector database is not empty" + if __name__ == "__main__": import asyncio + asyncio.run(main()) diff --git a/cognee/tests/test_weaviate.py b/cognee/tests/test_weaviate.py index 43ec30aaf..874f21347 100644 --- a/cognee/tests/test_weaviate.py +++ b/cognee/tests/test_weaviate.py @@ -7,19 +7,30 @@ logging.basicConfig(level=logging.DEBUG) + async def main(): cognee.config.set_vector_db_provider("weaviate") - data_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_weaviate")).resolve()) + data_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_weaviate") + ).resolve() + ) cognee.config.data_root_directory(data_directory_path) - cognee_directory_path = str(pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_weaviate")).resolve()) + cognee_directory_path = str( + pathlib.Path( + os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_weaviate") + ).resolve() + ) cognee.config.system_root_directory(cognee_directory_path) await cognee.prune.prune_data() - await cognee.prune.prune_system(metadata = True) + await cognee.prune.prune_system(metadata=True) dataset_name = "cs_explanations" - explanation_file_path = os.path.join(pathlib.Path(__file__).parent, "test_data/Natural_language_processing.txt") + explanation_file_path = os.path.join( + pathlib.Path(__file__).parent, "test_data/Natural_language_processing.txt" + ) await cognee.add([explanation_file_path], dataset_name) text = """A quantum computer is a computer that takes advantage of quantum mechanical phenomena. @@ -35,23 +46,24 @@ async def main(): await cognee.cognify([dataset_name]) from cognee.infrastructure.databases.vector import get_vector_engine + vector_engine = get_vector_engine() random_node = (await vector_engine.search("entity_name", "Quantum computer"))[0] random_node_name = random_node.payload["text"] - search_results = await cognee.search(SearchType.INSIGHTS, query_text = random_node_name) + search_results = await cognee.search(SearchType.INSIGHTS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted sentences are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.CHUNKS, query_text = random_node_name) + search_results = await cognee.search(SearchType.CHUNKS, query_text=random_node_name) assert len(search_results) != 0, "The search results list is empty." print("\n\nExtracted chunks are:\n") for result in search_results: print(f"{result}\n") - search_results = await cognee.search(SearchType.SUMMARIES, query_text = random_node_name) + search_results = await cognee.search(SearchType.SUMMARIES, query_text=random_node_name) assert len(search_results) != 0, "Query related summaries don't exist." print("\nExtracted summaries are:\n") for result in search_results: @@ -60,16 +72,18 @@ async def main(): history = await cognee.get_search_history() assert len(history) == 6, "Search history is not correct." - results = await brute_force_triplet_search('What is a quantum computer?') + results = await brute_force_triplet_search("What is a quantum computer?") assert len(results) > 0 await cognee.prune.prune_data() assert not os.path.isdir(data_directory_path), "Local data files are not deleted" await cognee.prune.prune_system(metadata=True) - collections = get_vector_engine().client.collections.list_all() + collections = get_vector_engine().client.collections.list_all() assert len(collections) == 0, "Weaviate vector database is not empty" + if __name__ == "__main__": import asyncio + asyncio.run(main()) diff --git a/cognee/tests/unit/infrastructure/databases/test_index_graph_edges.py b/cognee/tests/unit/infrastructure/databases/test_index_graph_edges.py index e4c1dac05..59ad606b0 100644 --- a/cognee/tests/unit/infrastructure/databases/test_index_graph_edges.py +++ b/cognee/tests/unit/infrastructure/databases/test_index_graph_edges.py @@ -1,21 +1,33 @@ import pytest from unittest.mock import AsyncMock, patch + @pytest.mark.asyncio async def test_index_graph_edges_success(): """Test that index_graph_edges uses the index datapoints and creates vector index.""" mock_graph_engine = AsyncMock() - mock_graph_engine.get_graph_data.return_value = (None, [ - [{"relationship_name": "rel1"}, {"relationship_name": "rel1"}], - [{"relationship_name": "rel2"}] - ]) + mock_graph_engine.get_graph_data.return_value = ( + None, + [ + [{"relationship_name": "rel1"}, {"relationship_name": "rel1"}], + [{"relationship_name": "rel2"}], + ], + ) mock_vector_engine = AsyncMock() - with patch("cognee.tasks.storage.index_graph_edges.get_graph_engine", return_value=mock_graph_engine), \ - patch("cognee.tasks.storage.index_graph_edges.get_vector_engine", return_value=mock_vector_engine): - + with ( + patch( + "cognee.tasks.storage.index_graph_edges.get_graph_engine", + return_value=mock_graph_engine, + ), + patch( + "cognee.tasks.storage.index_graph_edges.get_vector_engine", + return_value=mock_vector_engine, + ), + ): from cognee.tasks.storage.index_graph_edges import index_graph_edges + await index_graph_edges() mock_graph_engine.get_graph_data.assert_awaited_once() @@ -31,10 +43,18 @@ async def test_index_graph_edges_no_relationships(): mock_vector_engine = AsyncMock() - with patch("cognee.tasks.storage.index_graph_edges.get_graph_engine", return_value=mock_graph_engine), \ - patch("cognee.tasks.storage.index_graph_edges.get_vector_engine", return_value=mock_vector_engine): - + with ( + patch( + "cognee.tasks.storage.index_graph_edges.get_graph_engine", + return_value=mock_graph_engine, + ), + patch( + "cognee.tasks.storage.index_graph_edges.get_vector_engine", + return_value=mock_vector_engine, + ), + ): from cognee.tasks.storage.index_graph_edges import index_graph_edges + await index_graph_edges() mock_graph_engine.get_graph_data.assert_awaited_once() @@ -45,12 +65,14 @@ async def test_index_graph_edges_no_relationships(): @pytest.mark.asyncio async def test_index_graph_edges_initialization_error(): """Test that index_graph_edges raises a RuntimeError if initialization fails.""" - with patch("cognee.tasks.storage.index_graph_edges.get_graph_engine", side_effect=Exception("Graph engine failed")), \ - patch("cognee.tasks.storage.index_graph_edges.get_vector_engine", return_value=AsyncMock()): - + with ( + patch( + "cognee.tasks.storage.index_graph_edges.get_graph_engine", + side_effect=Exception("Graph engine failed"), + ), + patch("cognee.tasks.storage.index_graph_edges.get_vector_engine", return_value=AsyncMock()), + ): from cognee.tasks.storage.index_graph_edges import index_graph_edges with pytest.raises(RuntimeError, match="Initialization error"): await index_graph_edges() - - diff --git a/cognee/tests/unit/interfaces/graph/get_graph_from_huge_model_test.py b/cognee/tests/unit/interfaces/graph/get_graph_from_huge_model_test.py index 06c74c854..9900670f2 100644 --- a/cognee/tests/unit/interfaces/graph/get_graph_from_huge_model_test.py +++ b/cognee/tests/unit/interfaces/graph/get_graph_from_huge_model_test.py @@ -9,30 +9,25 @@ random.seed(1500) + class Repository(DataPoint): path: str - _metadata = { - "index_fields": [], - "type": "Repository" - } + _metadata = {"index_fields": [], "type": "Repository"} + class CodeFile(DataPoint): part_of: Repository contains: List["CodePart"] = [] depends_on: List["CodeFile"] = [] source_code: str - _metadata = { - "index_fields": [], - "type": "CodeFile" - } + _metadata = {"index_fields": [], "type": "CodeFile"} + class CodePart(DataPoint): part_of: CodeFile source_code: str - _metadata = { - "index_fields": [], - "type": "CodePart" - } + _metadata = {"index_fields": [], "type": "CodePart"} + CodeFile.model_rebuild() CodePart.model_rebuild() @@ -41,13 +36,13 @@ class CodePart(DataPoint): def nanoseconds_to_largest_unit(nanoseconds): # Define conversion factors conversion_factors = { - 'weeks': 7 * 24 * 60 * 60 * 1e9, - 'days': 24 * 60 * 60 * 1e9, - 'hours': 60 * 60 * 1e9, - 'minutes': 60 * 1e9, - 'seconds': 1e9, - 'miliseconds': 1e6, - 'microseconds': 1e3, + "weeks": 7 * 24 * 60 * 60 * 1e9, + "days": 24 * 60 * 60 * 1e9, + "hours": 60 * 60 * 1e9, + "minutes": 60 * 1e9, + "seconds": 1e9, + "miliseconds": 1e6, + "microseconds": 1e3, } # Iterate through conversion factors to find the largest unit @@ -57,30 +52,41 @@ def nanoseconds_to_largest_unit(nanoseconds): return converted_value, unit # If nanoseconds is smaller than a second - return nanoseconds, 'nanoseconds' + return nanoseconds, "nanoseconds" async def test_circular_reference_extraction(): - repo = Repository(path = "repo1") - - code_files = [CodeFile( - id = uuid5(NAMESPACE_OID, f"file{file_index}"), - source_code = "source code", - part_of = repo, - contains = [], - depends_on = [CodeFile( - id = uuid5(NAMESPACE_OID, f"file{random_id}"), - source_code = "source code", - part_of = repo, - depends_on = [], - ) for random_id in [random.randint(0, 1499) for _ in range(random.randint(0, 5))]], - ) for file_index in range(1500)] + repo = Repository(path="repo1") + + code_files = [ + CodeFile( + id=uuid5(NAMESPACE_OID, f"file{file_index}"), + source_code="source code", + part_of=repo, + contains=[], + depends_on=[ + CodeFile( + id=uuid5(NAMESPACE_OID, f"file{random_id}"), + source_code="source code", + part_of=repo, + depends_on=[], + ) + for random_id in [random.randint(0, 1499) for _ in range(random.randint(0, 5))] + ], + ) + for file_index in range(1500) + ] for code_file in code_files: - code_file.contains.extend([CodePart( - part_of = code_file, - source_code = f"Part {part_index}", - ) for part_index in range(random.randint(1, 20))]) + code_file.contains.extend( + [ + CodePart( + part_of=code_file, + source_code=f"Part {part_index}", + ) + for part_index in range(random.randint(1, 20)) + ] + ) nodes = [] edges = [] @@ -90,9 +96,12 @@ async def test_circular_reference_extraction(): start = time.perf_counter_ns() - results = await asyncio.gather(*[ - get_graph_from_model(code_file, added_nodes = added_nodes, added_edges = added_edges) for code_file in code_files - ]) + results = await asyncio.gather( + *[ + get_graph_from_model(code_file, added_nodes=added_nodes, added_edges=added_edges) + for code_file in code_files + ] + ) time_to_run = time.perf_counter_ns() - start @@ -105,5 +114,6 @@ async def test_circular_reference_extraction(): assert len(nodes) == 1501 assert len(edges) == 1501 * 20 + 1500 * 5 + if __name__ == "__main__": asyncio.run(test_circular_reference_extraction()) diff --git a/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py b/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py index 499dc9f3f..36fa71c25 100644 --- a/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py +++ b/cognee/tests/unit/interfaces/graph/get_graph_from_model_test.py @@ -9,55 +9,52 @@ class Document(DataPoint): path: str - _metadata = { - "index_fields": [], - "type": "Document" - } + _metadata = {"index_fields": [], "type": "Document"} + class DocumentChunk(DataPoint): part_of: Document text: str contains: List["Entity"] = None - _metadata = { - "index_fields": ["text"], - "type": "DocumentChunk" - } + _metadata = {"index_fields": ["text"], "type": "DocumentChunk"} + class EntityType(DataPoint): name: str - _metadata = { - "index_fields": ["name"], - "type": "EntityType" - } + _metadata = {"index_fields": ["name"], "type": "EntityType"} + class Entity(DataPoint): name: str is_type: EntityType - _metadata = { - "index_fields": ["name"], - "type": "Entity" - } + _metadata = {"index_fields": ["name"], "type": "Entity"} + DocumentChunk.model_rebuild() async def get_graph_from_model_test(): - document = Document(path = "file_path") - - document_chunks = [DocumentChunk( - id = uuid5(NAMESPACE_OID, f"file{file_index}"), - text = "some text", - part_of = document, - contains = [], - ) for file_index in range(1)] + document = Document(path="file_path") + + document_chunks = [ + DocumentChunk( + id=uuid5(NAMESPACE_OID, f"file{file_index}"), + text="some text", + part_of=document, + contains=[], + ) + for file_index in range(1) + ] for document_chunk in document_chunks: - document_chunk.contains.append(Entity( - name = f"Entity", - is_type = EntityType( - name = "Type 1", - ), - )) + document_chunk.contains.append( + Entity( + name="Entity", + is_type=EntityType( + name="Type 1", + ), + ) + ) nodes = [] edges = [] @@ -66,14 +63,17 @@ async def get_graph_from_model_test(): added_edges = {} visited_properties = {} - results = await asyncio.gather(*[ - get_graph_from_model( - document_chunk, - added_nodes = added_nodes, - added_edges = added_edges, - visited_properties = visited_properties, - ) for document_chunk in document_chunks - ]) + results = await asyncio.gather( + *[ + get_graph_from_model( + document_chunk, + added_nodes=added_nodes, + added_edges=added_edges, + visited_properties=visited_properties, + ) + for document_chunk in document_chunks + ] + ) for result_nodes, result_edges in results: nodes.extend(result_nodes) @@ -82,5 +82,6 @@ async def get_graph_from_model_test(): assert len(nodes) == 4 assert len(edges) == 3 + if __name__ == "__main__": asyncio.run(get_graph_from_model_test()) diff --git a/cognee/tests/unit/modules/graph/cognee_graph_elements_test.py b/cognee/tests/unit/modules/graph/cognee_graph_elements_test.py index 1d9bad07c..000856b12 100644 --- a/cognee/tests/unit/modules/graph/cognee_graph_elements_test.py +++ b/cognee/tests/unit/modules/graph/cognee_graph_elements_test.py @@ -9,7 +9,7 @@ def test_node_initialization(): """Test that a Node is initialized correctly.""" node = Node("node1", {"attr1": "value1"}, dimension=2) assert node.id == "node1" - assert node.attributes == {"attr1": "value1", 'vector_distance': np.inf} + assert node.attributes == {"attr1": "value1", "vector_distance": np.inf} assert len(node.status) == 2 assert np.all(node.status == 1) @@ -96,7 +96,7 @@ def test_edge_initialization(): edge = Edge(node1, node2, {"weight": 10}, directed=False, dimension=2) assert edge.node1 == node1 assert edge.node2 == node2 - assert edge.attributes == {'vector_distance': np.inf,"weight": 10} + assert edge.attributes == {"vector_distance": np.inf, "weight": 10} assert edge.directed is False assert len(edge.status) == 2 assert np.all(edge.status == 1) diff --git a/cognee/tests/unit/modules/retriever/test_description_to_codepart_search.py b/cognee/tests/unit/modules/retriever/test_description_to_codepart_search.py index 4c39883a9..5cceade71 100644 --- a/cognee/tests/unit/modules/retriever/test_description_to_codepart_search.py +++ b/cognee/tests/unit/modules/retriever/test_description_to_codepart_search.py @@ -2,7 +2,6 @@ from unittest.mock import AsyncMock, patch - @pytest.mark.asyncio async def test_code_description_to_code_part_no_results(): """Test that code_description_to_code_part handles no search results.""" @@ -12,11 +11,24 @@ async def test_code_description_to_code_part_no_results(): mock_vector_engine = AsyncMock() mock_vector_engine.search.return_value = [] - with patch("cognee.modules.retrieval.description_to_codepart_search.get_vector_engine", return_value=mock_vector_engine), \ - patch("cognee.modules.retrieval.description_to_codepart_search.get_graph_engine", return_value=AsyncMock()), \ - patch("cognee.modules.retrieval.description_to_codepart_search.CogneeGraph", return_value=AsyncMock()): + with ( + patch( + "cognee.modules.retrieval.description_to_codepart_search.get_vector_engine", + return_value=mock_vector_engine, + ), + patch( + "cognee.modules.retrieval.description_to_codepart_search.get_graph_engine", + return_value=AsyncMock(), + ), + patch( + "cognee.modules.retrieval.description_to_codepart_search.CogneeGraph", + return_value=AsyncMock(), + ), + ): + from cognee.modules.retrieval.description_to_codepart_search import ( + code_description_to_code_part, + ) - from cognee.modules.retrieval.description_to_codepart_search import code_description_to_code_part result = await code_description_to_code_part("search query", mock_user, 2) assert result == [] @@ -29,7 +41,10 @@ async def test_code_description_to_code_part_invalid_query(): mock_user = AsyncMock() with pytest.raises(ValueError, match="The query must be a non-empty string."): - from cognee.modules.retrieval.description_to_codepart_search import code_description_to_code_part + from cognee.modules.retrieval.description_to_codepart_search import ( + code_description_to_code_part, + ) + await code_description_to_code_part("", mock_user, 2) @@ -40,7 +55,10 @@ async def test_code_description_to_code_part_invalid_top_k(): mock_user = AsyncMock() with pytest.raises(ValueError, match="top_k must be a positive integer."): - from cognee.modules.retrieval.description_to_codepart_search import code_description_to_code_part + from cognee.modules.retrieval.description_to_codepart_search import ( + code_description_to_code_part, + ) + await code_description_to_code_part("search query", mock_user, 0) @@ -50,11 +68,23 @@ async def test_code_description_to_code_part_initialization_error(): mock_user = AsyncMock() - with patch("cognee.modules.retrieval.description_to_codepart_search.get_vector_engine", side_effect=Exception("Engine init failed")), \ - patch("cognee.modules.retrieval.description_to_codepart_search.get_graph_engine", return_value=AsyncMock()): - - from cognee.modules.retrieval.description_to_codepart_search import code_description_to_code_part - with pytest.raises(RuntimeError, match="System initialization error. Please try again later."): + with ( + patch( + "cognee.modules.retrieval.description_to_codepart_search.get_vector_engine", + side_effect=Exception("Engine init failed"), + ), + patch( + "cognee.modules.retrieval.description_to_codepart_search.get_graph_engine", + return_value=AsyncMock(), + ), + ): + from cognee.modules.retrieval.description_to_codepart_search import ( + code_description_to_code_part, + ) + + with pytest.raises( + RuntimeError, match="System initialization error. Please try again later." + ): await code_description_to_code_part("search query", mock_user, 2) @@ -67,10 +97,23 @@ async def test_code_description_to_code_part_execution_error(): mock_vector_engine = AsyncMock() mock_vector_engine.search.side_effect = Exception("Execution error") - with patch("cognee.modules.retrieval.description_to_codepart_search.get_vector_engine", return_value=mock_vector_engine), \ - patch("cognee.modules.retrieval.description_to_codepart_search.get_graph_engine", return_value=AsyncMock()), \ - patch("cognee.modules.retrieval.description_to_codepart_search.CogneeGraph", return_value=AsyncMock()): + with ( + patch( + "cognee.modules.retrieval.description_to_codepart_search.get_vector_engine", + return_value=mock_vector_engine, + ), + patch( + "cognee.modules.retrieval.description_to_codepart_search.get_graph_engine", + return_value=AsyncMock(), + ), + patch( + "cognee.modules.retrieval.description_to_codepart_search.CogneeGraph", + return_value=AsyncMock(), + ), + ): + from cognee.modules.retrieval.description_to_codepart_search import ( + code_description_to_code_part, + ) - from cognee.modules.retrieval.description_to_codepart_search import code_description_to_code_part with pytest.raises(RuntimeError, match="An error occurred while processing your request."): await code_description_to_code_part("search query", mock_user, 2) diff --git a/cognee/tests/unit/processing/chunks/chunk_by_paragraph_2_test.py b/cognee/tests/unit/processing/chunks/chunk_by_paragraph_2_test.py index 8e900727d..728b5cda4 100644 --- a/cognee/tests/unit/processing/chunks/chunk_by_paragraph_2_test.py +++ b/cognee/tests/unit/processing/chunks/chunk_by_paragraph_2_test.py @@ -29,9 +29,7 @@ def test_chunk_by_paragraph_isomorphism(input_text, paragraph_length, batch_para def test_paragraph_chunk_length(input_text, paragraph_length, batch_paragraphs): chunks = list(chunk_by_paragraph(input_text, paragraph_length, batch_paragraphs)) - chunk_lengths = np.array( - [len(list(chunk_by_word(chunk["text"]))) for chunk in chunks] - ) + chunk_lengths = np.array([len(list(chunk_by_word(chunk["text"]))) for chunk in chunks]) larger_chunks = chunk_lengths[chunk_lengths > paragraph_length] assert np.all( @@ -43,9 +41,7 @@ def test_paragraph_chunk_length(input_text, paragraph_length, batch_paragraphs): "input_text,paragraph_length,batch_paragraphs", list(product(list(INPUT_TEXTS.values()), paragraph_lengths, batch_paragraphs_vals)), ) -def test_chunk_by_paragraph_chunk_numbering( - input_text, paragraph_length, batch_paragraphs -): +def test_chunk_by_paragraph_chunk_numbering(input_text, paragraph_length, batch_paragraphs): chunks = chunk_by_paragraph(input_text, paragraph_length, batch_paragraphs) chunk_indices = np.array([chunk["chunk_index"] for chunk in chunks]) assert np.all( diff --git a/cognee/tests/unit/processing/chunks/chunk_by_word_test.py b/cognee/tests/unit/processing/chunks/chunk_by_word_test.py index 42523c106..fb26638cb 100644 --- a/cognee/tests/unit/processing/chunks/chunk_by_word_test.py +++ b/cognee/tests/unit/processing/chunks/chunk_by_word_test.py @@ -35,6 +35,4 @@ def test_chunk_by_word_splits(input_text): chunks = np.array(list(chunk_by_word(input_text))) space_test = np.array([" " not in chunk[0].strip() for chunk in chunks]) - assert np.all( - space_test - ), f"These chunks contain spaces within them: {chunks[space_test == False]}" + assert np.all(space_test), f"These chunks contain spaces within them: {chunks[~space_test]}" diff --git a/cognee/tests/unit/processing/utils/utils_test.py b/cognee/tests/unit/processing/utils/utils_test.py new file mode 100644 index 000000000..f8c325100 --- /dev/null +++ b/cognee/tests/unit/processing/utils/utils_test.py @@ -0,0 +1,131 @@ +import os +import pytest +import networkx as nx +import pandas as pd +from unittest.mock import patch, mock_open +from io import BytesIO +from uuid import uuid4 +from datetime import datetime, timezone +from cognee.shared.exceptions import IngestionError + +from cognee.shared.utils import ( + get_anonymous_id, + send_telemetry, + num_tokens_from_string, + get_file_content_hash, + trim_text_to_max_tokens, + prepare_edges, + prepare_nodes, + create_cognee_style_network_with_logo, + graph_to_tuple, +) + + +@pytest.fixture +def temp_dir(tmp_path): + return tmp_path + + +@patch("os.makedirs") +@patch("builtins.open", new_callable=mock_open, read_data=str(uuid4())) +def test_get_anonymous_id(mock_open_file, mock_makedirs, temp_dir): + os.environ["HOME"] = str(temp_dir) + anon_id = get_anonymous_id() + assert isinstance(anon_id, str) + assert len(anon_id) > 0 + + +# @patch("requests.post") +# def test_send_telemetry(mock_post): +# mock_post.return_value.status_code = 200 +# +# send_telemetry("test_event", "test_user", {"key": "value"}) +# mock_post.assert_called_once() +# +# args, kwargs = mock_post.call_args +# assert kwargs["json"]["event_name"] == "test_event" + +# +# @patch("tiktoken.encoding_for_model") +# def test_num_tokens_from_string(mock_encoding): +# mock_encoding.return_value.encode = lambda x: list(x) +# +# assert num_tokens_from_string("hello", "test_encoding") == 5 +# assert num_tokens_from_string("world", "test_encoding") == 5 +# + + +@patch("builtins.open", new_callable=mock_open, read_data=b"test_data") +def test_get_file_content_hash_file(mock_open_file): + import hashlib + + expected_hash = hashlib.md5(b"test_data").hexdigest() + result = get_file_content_hash("test_file.txt") + assert result == expected_hash + + +def test_get_file_content_hash_stream(): + stream = BytesIO(b"test_data") + import hashlib + + expected_hash = hashlib.md5(b"test_data").hexdigest() + result = get_file_content_hash(stream) + assert result == expected_hash + + +# def test_trim_text_to_max_tokens(): +# text = "This is a test string with multiple words." +# encoding_name = "test_encoding" +# +# with patch("tiktoken.get_encoding") as mock_get_encoding: +# mock_get_encoding.return_value.encode = lambda x: list(x) +# mock_get_encoding.return_value.decode = lambda x: "".join(x) +# +# result = trim_text_to_max_tokens(text, 5, encoding_name) +# assert result == text[:5] + + +def test_prepare_edges(): + graph = nx.MultiDiGraph() + graph.add_edge("A", "B", key="AB", weight=1) + edges_df = prepare_edges(graph, "source", "target", "key") + + assert isinstance(edges_df, pd.DataFrame) + assert len(edges_df) == 1 + + +def test_prepare_nodes(): + graph = nx.Graph() + graph.add_node(1, name="Node1") + nodes_df = prepare_nodes(graph) + + assert isinstance(nodes_df, pd.DataFrame) + assert len(nodes_df) == 1 + + +@pytest.mark.asyncio +async def test_create_cognee_style_network_with_logo(): + import networkx as nx + from unittest.mock import patch + from io import BytesIO + + # Create a sample graph + graph = nx.Graph() + graph.add_node(1, group="A") + graph.add_node(2, group="B") + graph.add_edge(1, 2) + + # Convert the graph to a tuple format for serialization + graph_tuple = graph_to_tuple(graph) + + result = await create_cognee_style_network_with_logo( + graph_tuple, + title="Test Network", + layout_func=nx.spring_layout, + layout_scale=3.0, + logo_alpha=0.5, + ) + + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 diff --git a/evals/deepeval_metrics.py b/evals/deepeval_metrics.py index b07d2e1ac..9ce1e9e4f 100644 --- a/evals/deepeval_metrics.py +++ b/evals/deepeval_metrics.py @@ -6,21 +6,17 @@ correctness_metric = GEval( name="Correctness", model="gpt-4o-mini", - evaluation_params=[ - LLMTestCaseParams.ACTUAL_OUTPUT, - LLMTestCaseParams.EXPECTED_OUTPUT - ], + evaluation_params=[LLMTestCaseParams.ACTUAL_OUTPUT, LLMTestCaseParams.EXPECTED_OUTPUT], evaluation_steps=[ - "Determine whether the actual output is factually correct based on the expected output." - ] + "Determine whether the actual output is factually correct based on the expected output." + ], ) class f1_score_metric(BaseMetric): - - """F1 score taken directly from the official hotpot benchmark + """F1 score taken directly from the official hotpot benchmark implementation and wrapped into a deepeval metric.""" - + def __init__(self, threshold: float = 0.5): self.threshold = threshold @@ -43,12 +39,12 @@ def is_successful(self): @property def __name__(self): return "Official hotpot F1 score" - -class em_score_metric(BaseMetric): - """Exact Match score taken directly from the official hotpot benchmark + +class em_score_metric(BaseMetric): + """Exact Match score taken directly from the official hotpot benchmark implementation and wrapped into a deepeval metric.""" - + def __init__(self, threshold: float = 0.5): self.threshold = threshold @@ -69,4 +65,4 @@ def is_successful(self): @property def __name__(self): - return "Official hotpot EM score" \ No newline at end of file + return "Official hotpot EM score" diff --git a/evals/eval_on_hotpot.py b/evals/eval_on_hotpot.py index e07e80e0c..5a95ea05d 100644 --- a/evals/eval_on_hotpot.py +++ b/evals/eval_on_hotpot.py @@ -34,19 +34,25 @@ async def answer_without_cognee(instance): ) return answer_prediction + async def answer_with_cognee(instance): await cognee.prune.prune_data() await cognee.prune.prune_system(metadata=True) - - for (title, sentences) in instance["context"]: - await cognee.add("\n".join(sentences), dataset_name = "HotPotQA") - - await cognee.cognify("HotPotQA") - - search_results = await cognee.search( - SearchType.INSIGHTS, query_text=instance["question"] + + for title, sentences in instance["context"]: + await cognee.add("\n".join(sentences), dataset_name="HotPotQA") + + for n in range(1, 4): + print(n) + + await cognee.cognify("HotPotQA") + + search_results = await cognee.search(SearchType.INSIGHTS, query_text=instance["question"]) + search_results_second = await cognee.search( + SearchType.SUMMARIES, query_text=instance["question"] ) - + search_results = search_results + search_results_second + args = { "question": instance["question"], "context": search_results, @@ -60,61 +66,67 @@ async def answer_with_cognee(instance): system_prompt=system_prompt, response_model=str, ) - + return answer_prediction async def eval_answers(instances, answers, eval_metric): test_cases = [] - + for instance, answer in zip(instances, answers): test_case = LLMTestCase( - input=instance["question"], - actual_output=answer, - expected_output=instance["answer"] + input=instance["question"], actual_output=answer, expected_output=instance["answer"] ) test_cases.append(test_case) - + eval_set = EvaluationDataset(test_cases) eval_results = eval_set.evaluate([eval_metric]) - + return eval_results + async def eval_on_hotpotQA(answer_provider, num_samples, eval_metric): base_config = get_base_config() data_root_dir = base_config.data_root_directory - + if not Path(data_root_dir).exists(): Path(data_root_dir).mkdir() - + filepath = data_root_dir / Path("hotpot_dev_fullwiki_v1.json") if not filepath.exists(): - url = 'http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json' + url = "http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json" wget.download(url, out=data_root_dir) - + with open(filepath, "r") as file: dataset = json.load(file) - + instances = dataset if not num_samples else dataset[:num_samples] answers = [] for instance in tqdm(instances, desc="Getting answers"): answer = await answer_provider(instance) answers.append(answer) - + eval_results = await eval_answers(instances, answers, eval_metric) - avg_score = statistics.mean([result.metrics_data[0].score for result in eval_results.test_results]) - + avg_score = statistics.mean( + [result.metrics_data[0].score for result in eval_results.test_results] + ) + return avg_score + if __name__ == "__main__": parser = argparse.ArgumentParser() - + parser.add_argument("--with_cognee", action="store_true") parser.add_argument("--num_samples", type=int, default=500) - parser.add_argument("--metric", type=str, default="correctness_metric", - help="Valid options are Deepeval metrics (e.g. AnswerRelevancyMetric) \ - and metrics defined in evals/deepeval_metrics.py, e.g. f1_score_metric") - + parser.add_argument( + "--metric", + type=str, + default="correctness_metric", + help="Valid options are Deepeval metrics (e.g. AnswerRelevancyMetric) \ + and metrics defined in evals/deepeval_metrics.py, e.g. f1_score_metric", + ) + args = parser.parse_args() try: @@ -124,11 +136,11 @@ async def eval_on_hotpotQA(answer_provider, num_samples, eval_metric): metric = getattr(evals.deepeval_metrics, args.metric) if isinstance(metric, type): metric = metric() - + if args.with_cognee: answer_provider = answer_with_cognee else: answer_provider = answer_without_cognee - + avg_score = asyncio.run(eval_on_hotpotQA(answer_provider, args.num_samples, metric)) - print(f"Average {args.metric}: {avg_score}") \ No newline at end of file + print(f"Average {args.metric}: {avg_score}") diff --git a/evals/eval_swe_bench.py b/evals/eval_swe_bench.py index 6c2280d80..789c95ab4 100644 --- a/evals/eval_swe_bench.py +++ b/evals/eval_swe_bench.py @@ -11,8 +11,7 @@ from cognee.api.v1.search import SearchType from cognee.infrastructure.llm.get_llm_client import get_llm_client from cognee.infrastructure.llm.prompts import read_query_prompt -from cognee.modules.retrieval.brute_force_triplet_search import \ - brute_force_triplet_search +from cognee.modules.retrieval.brute_force_triplet_search import brute_force_triplet_search from cognee.shared.utils import render_graph from evals.eval_utils import download_github_repo, retrieved_edges_to_string @@ -27,41 +26,42 @@ def check_install_package(package_name): return True except ImportError: try: - subprocess.check_call( - [sys.executable, "-m", "pip", "install", package_name] - ) + subprocess.check_call([sys.executable, "-m", "pip", "install", package_name]) return True except subprocess.CalledProcessError: return False async def generate_patch_with_cognee(instance, llm_client, search_type=SearchType.CHUNKS): - repo_path = download_github_repo(instance, '../RAW_GIT_REPOS') + repo_path = download_github_repo(instance, "../RAW_GIT_REPOS") pipeline = await run_code_graph_pipeline(repo_path) async for result in pipeline: print(result) - print('Here we have the repo under the repo_path') + print("Here we have the repo under the repo_path") await render_graph(None, include_labels=True, include_nodes=True) - problem_statement = instance['problem_statement'] + problem_statement = instance["problem_statement"] instructions = read_query_prompt("patch_gen_kg_instructions.txt") - retrieved_edges = await brute_force_triplet_search(problem_statement, top_k=3, - collections=["data_point_source_code", "data_point_text"]) + retrieved_edges = await brute_force_triplet_search( + problem_statement, top_k=3, collections=["data_point_source_code", "data_point_text"] + ) retrieved_edges_str = retrieved_edges_to_string(retrieved_edges) - prompt = "\n".join([ - problem_statement, - "", - PATCH_EXAMPLE, - "", - "These are the retrieved edges:", - retrieved_edges_str - ]) + prompt = "\n".join( + [ + problem_statement, + "", + PATCH_EXAMPLE, + "", + "These are the retrieved edges:", + retrieved_edges_str, + ] + ) llm_client = get_llm_client() answer_prediction = await llm_client.acreate_structured_output( @@ -94,10 +94,7 @@ async def get_preds(dataset, with_cognee=True): model_name = "without_cognee" pred_func = generate_patch_without_cognee - futures = [ - (instance["instance_id"], pred_func(instance, llm_client)) - for instance in dataset - ] + futures = [(instance["instance_id"], pred_func(instance, llm_client)) for instance in dataset] model_patches = await asyncio.gather(*[x[1] for x in futures]) preds = [ @@ -113,9 +110,8 @@ async def get_preds(dataset, with_cognee=True): async def main(): - parser = argparse.ArgumentParser( - description="Run LLM predictions on SWE-bench dataset") - parser.add_argument('--cognee_off', action='store_true') + parser = argparse.ArgumentParser(description="Run LLM predictions on SWE-bench dataset") + parser.add_argument("--cognee_off", action="store_true") parser.add_argument("--max_workers", type=int, required=True) args = parser.parse_args() @@ -123,17 +119,16 @@ async def main(): check_install_package(dependency) if args.cognee_off: - dataset_name = 'princeton-nlp/SWE-bench_Lite_bm25_13K' - dataset = load_swebench_dataset(dataset_name, split='test') + dataset_name = "princeton-nlp/SWE-bench_Lite_bm25_13K" + dataset = load_swebench_dataset(dataset_name, split="test") predictions_path = "preds_nocognee.json" if not Path(predictions_path).exists(): preds = await get_preds(dataset, with_cognee=False) with open(predictions_path, "w") as file: json.dump(preds, file) else: - dataset_name = 'princeton-nlp/SWE-bench_Lite' - swe_dataset = load_swebench_dataset( - dataset_name, split='test')[:1] + dataset_name = "princeton-nlp/SWE-bench_Lite" + swe_dataset = load_swebench_dataset(dataset_name, split="test")[:1] predictions_path = "preds.json" preds = await get_preds(swe_dataset, with_cognee=not args.cognee_off) with open(predictions_path, "w") as file: diff --git a/evals/eval_utils.py b/evals/eval_utils.py index 26c4ec2b8..6241cd4dd 100644 --- a/evals/eval_utils.py +++ b/evals/eval_utils.py @@ -15,9 +15,9 @@ def download_github_repo(instance, output_dir): Returns: str: Path to the downloaded repository. """ - repo_owner_repo = instance['repo'] - base_commit = instance['base_commit'] - instance_id = instance['instance_id'] + repo_owner_repo = instance["repo"] + base_commit = instance["base_commit"] + instance_id = instance["instance_id"] repo_url = f"https://github.com/{repo_owner_repo}.git" @@ -30,7 +30,6 @@ def download_github_repo(instance, output_dir): else: print(f"Repository already exists at {repo_path}.") - repo = Repo(repo_path) repo.git.checkout(base_commit) @@ -69,4 +68,4 @@ def retrieved_edges_to_string(retrieved_edges): relationship_type = edge.attributes["relationship_type"] edge_str = f"{node_to_string(edge.node1)} {relationship_type} {node_to_string(edge.node2)}" edge_strings.append(edge_str) - return "\n".join(edge_strings) \ No newline at end of file + return "\n".join(edge_strings) diff --git a/evals/generate_test_set.py b/evals/generate_test_set.py index 58bf4222a..ccac8d0e9 100644 --- a/evals/generate_test_set.py +++ b/evals/generate_test_set.py @@ -3,6 +3,10 @@ import dotenv from deepeval.test_case import LLMTestCase +# import pytest +# from deepeval import assert_test +from deepeval.metrics import AnswerRelevancyMetric + dotenv.load_dotenv() # synthesizer = Synthesizer() @@ -22,7 +26,7 @@ dataset = EvaluationDataset() dataset.generate_goldens_from_docs( - document_paths=['natural_language_processing.txt', 'soldiers_home.pdf', 'trump.txt'], + document_paths=["natural_language_processing.txt", "soldiers_home.pdf", "trump.txt"], max_goldens_per_document=10, num_evolutions=5, enable_breadth_evolve=True, @@ -33,16 +37,9 @@ print(dataset) -import pytest -from deepeval import assert_test -from deepeval.metrics import AnswerRelevancyMetric - - answer_relevancy_metric = AnswerRelevancyMetric(threshold=0.5) -from deepeval import evaluate +# from deepeval import evaluate # evaluate(dataset, [answer_relevancy_metric]) - - diff --git a/evals/official_hotpot_metrics.py b/evals/official_hotpot_metrics.py index b598e90d3..70444f7be 100644 --- a/evals/official_hotpot_metrics.py +++ b/evals/official_hotpot_metrics.py @@ -11,16 +11,15 @@ def normalize_answer(s): - def remove_articles(text): - return re.sub(r'\b(a|an|the)\b', ' ', text) + return re.sub(r"\b(a|an|the)\b", " ", text) def white_space_fix(text): - return ' '.join(text.split()) + return " ".join(text.split()) def remove_punc(text): exclude = set(string.punctuation) - return ''.join(ch for ch in text if ch not in exclude) + return "".join(ch for ch in text if ch not in exclude) def lower(text): return text.lower() @@ -34,9 +33,15 @@ def f1_score(prediction, ground_truth): ZERO_METRIC = (0, 0, 0) - if normalized_prediction in ['yes', 'no', 'noanswer'] and normalized_prediction != normalized_ground_truth: + if ( + normalized_prediction in ["yes", "no", "noanswer"] + and normalized_prediction != normalized_ground_truth + ): return ZERO_METRIC - if normalized_ground_truth in ['yes', 'no', 'noanswer'] and normalized_prediction != normalized_ground_truth: + if ( + normalized_ground_truth in ["yes", "no", "noanswer"] + and normalized_prediction != normalized_ground_truth + ): return ZERO_METRIC prediction_tokens = normalized_prediction.split() @@ -52,17 +57,19 @@ def f1_score(prediction, ground_truth): def exact_match_score(prediction, ground_truth): - return (normalize_answer(prediction) == normalize_answer(ground_truth)) + return normalize_answer(prediction) == normalize_answer(ground_truth) + def update_answer(metrics, prediction, gold): em = exact_match_score(prediction, gold) f1, prec, recall = f1_score(prediction, gold) - metrics['em'] += float(em) - metrics['f1'] += f1 - metrics['prec'] += prec - metrics['recall'] += recall + metrics["em"] += float(em) + metrics["f1"] += f1 + metrics["prec"] += prec + metrics["recall"] += recall return em, prec, recall + def update_sp(metrics, prediction, gold): cur_sp_pred = set(map(tuple, prediction)) gold_sp_pred = set(map(tuple, gold)) @@ -79,8 +86,8 @@ def update_sp(metrics, prediction, gold): recall = 1.0 * tp / (tp + fn) if tp + fn > 0 else 0.0 f1 = 2 * prec * recall / (prec + recall) if prec + recall > 0 else 0.0 em = 1.0 if fp + fn == 0 else 0.0 - metrics['sp_em'] += em - metrics['sp_f1'] += f1 - metrics['sp_prec'] += prec - metrics['sp_recall'] += recall + metrics["sp_em"] += em + metrics["sp_f1"] += f1 + metrics["sp_prec"] += prec + metrics["sp_recall"] += recall return em, prec, recall diff --git a/evals/simple_rag_vs_cognee_eval.py b/evals/simple_rag_vs_cognee_eval.py index 5d1ddb4ca..82b1df600 100644 --- a/evals/simple_rag_vs_cognee_eval.py +++ b/evals/simple_rag_vs_cognee_eval.py @@ -1,13 +1,19 @@ from deepeval.dataset import EvaluationDataset from pydantic import BaseModel - +import os from typing import List, Type from deepeval.test_case import LLMTestCase import dotenv +from cognee.infrastructure.llm.get_llm_client import get_llm_client +from cognee.infrastructure.databases.vector import get_vector_engine +from cognee.base_config import get_base_config + +import logging + +logger = logging.getLogger(__name__) dotenv.load_dotenv() -from cognee.infrastructure.llm.get_llm_client import get_llm_client dataset = EvaluationDataset() dataset.add_test_cases_from_json_file( @@ -16,7 +22,7 @@ input_key_name="input", actual_output_key_name="actual_output", expected_output_key_name="expected_output", - context_key_name="context" + context_key_name="context", ) print(dataset) @@ -38,32 +44,26 @@ print(dataset) +class AnswerModel(BaseModel): + response: str -import logging - -logger = logging.getLogger(__name__) - -class AnswerModel(BaseModel): - response:str -def get_answer_base(content: str, context:str, response_model: Type[BaseModel]): +def get_answer_base(content: str, context: str, response_model: Type[BaseModel]): llm_client = get_llm_client() system_prompt = "THIS IS YOUR CONTEXT:" + str(context) - return llm_client.create_structured_output(content, system_prompt, response_model) -def get_answer(content: str,context, model: Type[BaseModel]= AnswerModel): + return llm_client.create_structured_output(content, system_prompt, response_model) + +def get_answer(content: str, context, model: Type[BaseModel] = AnswerModel): try: - return (get_answer_base( - content, - context, - model - )) + return get_answer_base(content, context, model) except Exception as error: - logger.error("Error extracting cognitive layers from content: %s", error, exc_info = True) + logger.error("Error extracting cognitive layers from content: %s", error, exc_info=True) raise error + async def run_cognify_base_rag(): from cognee.api.v1.add import add from cognee.api.v1.prune import prune @@ -74,17 +74,10 @@ async def run_cognify_base_rag(): await add("data://test_datasets", "initial_test") graph = await cognify("initial_test") + return graph - - pass - - -import os -from cognee.base_config import get_base_config -from cognee.infrastructure.databases.vector import get_vector_engine - -async def cognify_search_base_rag(content:str, context:str): +async def cognify_search_base_rag(content: str, context: str): base_config = get_base_config() cognee_directory_path = os.path.abspath(".cognee_system") @@ -97,23 +90,24 @@ async def cognify_search_base_rag(content:str, context:str): print("results", return_) return return_ -async def cognify_search_graph(content:str, context:str): + +async def cognify_search_graph(content: str, context: str): from cognee.api.v1.search import search, SearchType - params = {'query': 'Donald Trump'} + + params = {"query": "Donald Trump"} results = await search(SearchType.INSIGHTS, params) print("results", results) return results - def convert_goldens_to_test_cases(test_cases_raw: List[LLMTestCase]) -> List[LLMTestCase]: test_cases = [] for case in test_cases_raw: test_case = LLMTestCase( input=case.input, # Generate actual output using the 'input' and 'additional_metadata' - actual_output= str(get_answer(case.input, case.context).model_dump()['response']), + actual_output=str(get_answer(case.input, case.context).model_dump()["response"]), expected_output=case.expected_output, context=case.context, retrieval_context=["retrieval_context"], @@ -121,6 +115,7 @@ def convert_goldens_to_test_cases(test_cases_raw: List[LLMTestCase]) -> List[LLM test_cases.append(test_case) return test_cases + # # Data preprocessing before setting the dataset test cases # dataset.test_cases = convert_goldens_to_test_cases(dataset.test_cases) # @@ -133,13 +128,13 @@ def convert_goldens_to_test_cases(test_cases_raw: List[LLMTestCase]) -> List[LLM if __name__ == "__main__": - import asyncio async def main(): # await run_cognify_base_rag() # await cognify_search_base_rag("show_all_processes", "context") await cognify_search_graph("show_all_processes", "context") + asyncio.run(main()) # run_cognify_base_rag_and_search() # # Data preprocessing before setting the dataset test cases @@ -147,4 +142,4 @@ async def main(): # from deepeval.metrics import HallucinationMetric # metric = HallucinationMetric() # dataset.evaluate([metric]) - pass \ No newline at end of file + pass diff --git a/examples/python/code_graph_example.py b/examples/python/code_graph_example.py index 44ab33aad..c90a0b606 100644 --- a/examples/python/code_graph_example.py +++ b/examples/python/code_graph_example.py @@ -8,9 +8,15 @@ async def main(repo_path, include_docs): async for result in run_code_graph_pipeline(repo_path, include_docs): print(result) + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--repo_path", type=str, required=True, help="Path to the repository") - parser.add_argument("--include_docs", type=lambda x: x.lower() in ("true", "1"), default=True, help="Whether or not to process non-code files") + parser.add_argument( + "--include_docs", + type=lambda x: x.lower() in ("true", "1"), + default=True, + help="Whether or not to process non-code files", + ) args = parser.parse_args() - asyncio.run(main(args.repo_path, args.include_docs)) \ No newline at end of file + asyncio.run(main(args.repo_path, args.include_docs)) diff --git a/examples/python/dynamic_steps_example.py b/examples/python/dynamic_steps_example.py index 7c0af8f0c..165d907d3 100644 --- a/examples/python/dynamic_steps_example.py +++ b/examples/python/dynamic_steps_example.py @@ -160,6 +160,7 @@ Negotiation and Relationship Building """ + async def main(enable_steps): # Step 1: Reset data and system state if enable_steps.get("prune_data"): @@ -184,10 +185,13 @@ async def main(enable_steps): # Step 4: Query insights if enable_steps.get("retriever"): - results = await brute_force_triplet_search('Who has the most experience with graphic design?') + results = await brute_force_triplet_search( + "Who has the most experience with graphic design?" + ) print(format_triplets(results)) -if __name__ == '__main__': + +if __name__ == "__main__": setup_logging(logging.ERROR) rebuild_kg = True @@ -197,7 +201,7 @@ async def main(enable_steps): "prune_system": rebuild_kg, "add_text": rebuild_kg, "cognify": rebuild_kg, - "retriever": retrieve + "retriever": retrieve, } asyncio.run(main(steps_to_enable)) diff --git a/examples/python/graphiti_example.py b/examples/python/graphiti_example.py index a48fa9b4c..248361321 100644 --- a/examples/python/graphiti_example.py +++ b/examples/python/graphiti_example.py @@ -4,7 +4,9 @@ from cognee.api.v1.search import SearchType from cognee.modules.pipelines import Task, run_tasks from cognee.tasks.temporal_awareness import ( - build_graph_with_temporal_awareness, search_graph_with_temporal_awareness) + build_graph_with_temporal_awareness, + search_graph_with_temporal_awareness, +) text_list = [ "Kamala Harris is the Attorney General of California. She was previously " @@ -12,18 +14,20 @@ "As AG, Harris was in office from January 3, 2011 – January 3, 2017", ] -async def main(): +async def main(): tasks = [ Task(build_graph_with_temporal_awareness, text_list=text_list), - Task(search_graph_with_temporal_awareness, query='Who was the California Attorney General?') + Task( + search_graph_with_temporal_awareness, query="Who was the California Attorney General?" + ), ] - + pipeline = run_tasks(tasks) - + async for result in pipeline: print(result) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/examples/python/simple_example.py b/examples/python/simple_example.py index 55b07c4c3..bf3c95de1 100644 --- a/examples/python/simple_example.py +++ b/examples/python/simple_example.py @@ -20,47 +20,50 @@ async def main(): Natural language processing (NLP) is an interdisciplinary subfield of computer science and information retrieval. """ - + print("Adding text to cognee:") - print(text.strip()) + print(text.strip()) # Add the text, and make it available for cognify await cognee.add(text) print("Text added successfully.\n") - print("Running cognify to create knowledge graph...\n") print("Cognify process steps:") print("1. Classifying the document: Determining the type and category of the input text.") - print("2. Checking permissions: Ensuring the user has the necessary rights to process the text.") - print("3. Extracting text chunks: Breaking down the text into sentences or phrases for analysis.") + print( + "2. Checking permissions: Ensuring the user has the necessary rights to process the text." + ) + print( + "3. Extracting text chunks: Breaking down the text into sentences or phrases for analysis." + ) print("4. Adding data points: Storing the extracted chunks for processing.") - print("5. Generating knowledge graph: Extracting entities and relationships to form a knowledge graph.") + print( + "5. Generating knowledge graph: Extracting entities and relationships to form a knowledge graph." + ) print("6. Summarizing text: Creating concise summaries of the content for quick insights.\n") - + # Use LLMs and cognee to create knowledge graph await cognee.cognify() print("Cognify process complete.\n") - - query_text = 'Tell me about NLP' + query_text = "Tell me about NLP" print(f"Searching cognee for insights with query: '{query_text}'") # Query cognee for insights on the added text - search_results = await cognee.search( - SearchType.INSIGHTS, query_text=query_text - ) - + search_results = await cognee.search(SearchType.INSIGHTS, query_text=query_text) + print("Search results:") # Display results for result_text in search_results: print(result_text) # Example output: - # ({'id': UUID('bc338a39-64d6-549a-acec-da60846dd90d'), 'updated_at': datetime.datetime(2024, 11, 21, 12, 23, 1, 211808, tzinfo=datetime.timezone.utc), 'name': 'natural language processing', 'description': 'An interdisciplinary subfield of computer science and information retrieval.'}, {'relationship_name': 'is_a_subfield_of', 'source_node_id': UUID('bc338a39-64d6-549a-acec-da60846dd90d'), 'target_node_id': UUID('6218dbab-eb6a-5759-a864-b3419755ffe0'), 'updated_at': datetime.datetime(2024, 11, 21, 12, 23, 15, 473137, tzinfo=datetime.timezone.utc)}, {'id': UUID('6218dbab-eb6a-5759-a864-b3419755ffe0'), 'updated_at': datetime.datetime(2024, 11, 21, 12, 23, 1, 211808, tzinfo=datetime.timezone.utc), 'name': 'computer science', 'description': 'The study of computation and information processing.'}) - # (...) - # It represents nodes and relationships in the knowledge graph: - # - The first element is the source node (e.g., 'natural language processing'). - # - The second element is the relationship between nodes (e.g., 'is_a_subfield_of'). - # - The third element is the target node (e.g., 'computer science'). + # ({'id': UUID('bc338a39-64d6-549a-acec-da60846dd90d'), 'updated_at': datetime.datetime(2024, 11, 21, 12, 23, 1, 211808, tzinfo=datetime.timezone.utc), 'name': 'natural language processing', 'description': 'An interdisciplinary subfield of computer science and information retrieval.'}, {'relationship_name': 'is_a_subfield_of', 'source_node_id': UUID('bc338a39-64d6-549a-acec-da60846dd90d'), 'target_node_id': UUID('6218dbab-eb6a-5759-a864-b3419755ffe0'), 'updated_at': datetime.datetime(2024, 11, 21, 12, 23, 15, 473137, tzinfo=datetime.timezone.utc)}, {'id': UUID('6218dbab-eb6a-5759-a864-b3419755ffe0'), 'updated_at': datetime.datetime(2024, 11, 21, 12, 23, 1, 211808, tzinfo=datetime.timezone.utc), 'name': 'computer science', 'description': 'The study of computation and information processing.'}) + # (...) + # It represents nodes and relationships in the knowledge graph: + # - The first element is the source node (e.g., 'natural language processing'). + # - The second element is the relationship between nodes (e.g., 'is_a_subfield_of'). + # - The third element is the target node (e.g., 'computer science'). + -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/notebooks/cognee_code_graph_demo.ipynb b/notebooks/cognee_code_graph_demo.ipynb index ef22afe58..d5bde67ef 100644 --- a/notebooks/cognee_code_graph_demo.ipynb +++ b/notebooks/cognee_code_graph_demo.ipynb @@ -7,8 +7,9 @@ "outputs": [], "source": [ "import os\n", - "os.environ['GRAPHISTRY_USERNAME'] = input(\"Please enter your graphistry username\")\n", - "os.environ['GRAPHISTRY_PASSWORD'] = input(\"Please enter your graphistry password\")" + "\n", + "os.environ[\"GRAPHISTRY_USERNAME\"] = input(\"Please enter your graphistry username\")\n", + "os.environ[\"GRAPHISTRY_PASSWORD\"] = input(\"Please enter your graphistry password\")" ] }, { @@ -23,13 +24,17 @@ "from cognee.infrastructure.databases.relational import create_db_and_tables\n", "\n", "notebook_path = os.path.abspath(\"\")\n", - "data_directory_path = str(pathlib.Path(os.path.join(notebook_path, \".data_storage/code_graph\")).resolve())\n", + "data_directory_path = str(\n", + " pathlib.Path(os.path.join(notebook_path, \".data_storage/code_graph\")).resolve()\n", + ")\n", "cognee.config.data_root_directory(data_directory_path)\n", - "cognee_directory_path = str(pathlib.Path(os.path.join(notebook_path, \".cognee_system/code_graph\")).resolve())\n", + "cognee_directory_path = str(\n", + " pathlib.Path(os.path.join(notebook_path, \".cognee_system/code_graph\")).resolve()\n", + ")\n", "cognee.config.system_root_directory(cognee_directory_path)\n", "\n", "await cognee.prune.prune_data()\n", - "await cognee.prune.prune_system(metadata = True)\n", + "await cognee.prune.prune_system(metadata=True)\n", "\n", "await create_db_and_tables()" ] @@ -53,8 +58,8 @@ "git.Repo.clone_from(\n", " \"git@github.com:microsoft/graphrag.git\",\n", " Path(repo_clone_location),\n", - " branch = \"main\",\n", - " single_branch = True,\n", + " branch=\"main\",\n", + " single_branch=True,\n", ")" ] }, @@ -64,16 +69,20 @@ "metadata": {}, "outputs": [], "source": [ - "from cognee.tasks.repo_processor import enrich_dependency_graph, expand_dependency_graph, get_repo_file_dependencies\n", + "from cognee.tasks.repo_processor import (\n", + " enrich_dependency_graph,\n", + " expand_dependency_graph,\n", + " get_repo_file_dependencies,\n", + ")\n", "from cognee.tasks.storage import add_data_points\n", "from cognee.modules.pipelines.tasks.Task import Task\n", "\n", "tasks = [\n", " Task(get_repo_file_dependencies),\n", - " Task(add_data_points, task_config = { \"batch_size\": 50 }),\n", - " Task(enrich_dependency_graph, task_config = { \"batch_size\": 50 }),\n", - " Task(expand_dependency_graph, task_config = { \"batch_size\": 50 }),\n", - " Task(add_data_points, task_config = { \"batch_size\": 50 }),\n", + " Task(add_data_points, task_config={\"batch_size\": 50}),\n", + " Task(enrich_dependency_graph, task_config={\"batch_size\": 50}),\n", + " Task(expand_dependency_graph, task_config={\"batch_size\": 50}),\n", + " Task(add_data_points, task_config={\"batch_size\": 50}),\n", "]" ] }, @@ -101,19 +110,20 @@ "outputs": [], "source": [ "from cognee.shared.utils import render_graph\n", - "await render_graph(None, include_nodes = True, include_labels = True)" + "\n", + "await render_graph(None, include_nodes=True, include_labels=True)" ] }, { - "metadata": {}, "cell_type": "markdown", + "metadata": {}, "source": "# Let's check the evaluations" }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from evals.eval_on_hotpot import eval_on_hotpotQA\n", "from evals.eval_on_hotpot import answer_with_cognee\n", @@ -133,8 +143,8 @@ "metadata": {}, "outputs": [], "source": [ - "answer_provider = answer_with_cognee # For native LLM answers use answer_without_cognee\n", - "num_samples = 10 # With cognee, it takes ~1m10s per sample\n", + "answer_provider = answer_with_cognee # For native LLM answers use answer_without_cognee\n", + "num_samples = 10 # With cognee, it takes ~1m10s per sample\n", "\n", "base_config = get_base_config()\n", "data_root_dir = base_config.data_root_directory\n", @@ -144,7 +154,7 @@ "\n", "filepath = data_root_dir / Path(\"hotpot_dev_fullwiki_v1.json\")\n", "if not filepath.exists():\n", - " url = 'http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json'\n", + " url = \"http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json\"\n", " wget.download(url, out=data_root_dir)\n", "\n", "with open(filepath, \"r\") as file:\n", diff --git a/notebooks/cognee_demo.ipynb b/notebooks/cognee_demo.ipynb index de8c07cfb..f59f6338c 100644 --- a/notebooks/cognee_demo.ipynb +++ b/notebooks/cognee_demo.ipynb @@ -76,7 +76,7 @@ }, { "cell_type": "markdown", - "id": "b6a98710-a14b-4a14-bb56-d3ae055e94d9", + "id": "1bf1fa3631dc03ed", "metadata": {}, "source": [ "#### The problem lies in the nature of the search. If you just find some keywords, and return one or many documents from vectorstore this way, you will have an issue with the the way you would use to organise and prioritise documents. \n" @@ -92,7 +92,7 @@ }, { "cell_type": "markdown", - "id": "b6a98710-a14b-4a14-bb56-d3ae055e94d9", + "id": "390e0d0805096f80", "metadata": {}, "source": [ "## Semantic similarity search is not magic\n", @@ -265,15 +265,13 @@ }, { "cell_type": "code", - "execution_count": null, "id": "df16431d0f48b006", "metadata": { "ExecuteTime": { - "end_time": "2024-09-20T14:02:48.519686Z", - "start_time": "2024-09-20T14:02:48.515589Z" + "end_time": "2024-12-24T11:53:59.351640Z", + "start_time": "2024-12-24T11:53:59.347420Z" } }, - "outputs": [], "source": [ "job_position = \"\"\"Senior Data Scientist (Machine Learning)\n", "\n", @@ -300,19 +298,19 @@ "Strong problem-solving skills and attention to detail.\n", "Candidate CVs\n", "\"\"\"\n" - ] + ], + "outputs": [], + "execution_count": 1 }, { "cell_type": "code", - "execution_count": null, "id": "9086abf3af077ab4", "metadata": { "ExecuteTime": { - "end_time": "2024-09-20T14:02:49.120838Z", - "start_time": "2024-09-20T14:02:49.118294Z" + "end_time": "2024-12-24T11:53:59.365410Z", + "start_time": "2024-12-24T11:53:59.363662Z" } }, - "outputs": [], "source": [ "job_1 = \"\"\"\n", "CV 1: Relevant\n", @@ -345,19 +343,19 @@ "Big Data Technologies: Hadoop, Spark\n", "Data Visualization: Tableau, Matplotlib\n", "\"\"\"" - ] + ], + "outputs": [], + "execution_count": 2 }, { "cell_type": "code", - "execution_count": null, "id": "a9de0cc07f798b7f", "metadata": { "ExecuteTime": { - "end_time": "2024-09-20T14:02:49.675003Z", - "start_time": "2024-09-20T14:02:49.671615Z" + "end_time": "2024-12-24T11:53:59.372957Z", + "start_time": "2024-12-24T11:53:59.371152Z" } }, - "outputs": [], "source": [ "job_2 = \"\"\"\n", "CV 2: Relevant\n", @@ -389,19 +387,19 @@ "Data Visualization: Seaborn, Plotly\n", "Databases: MySQL, MongoDB\n", "\"\"\"" - ] + ], + "outputs": [], + "execution_count": 3 }, { "cell_type": "code", - "execution_count": null, "id": "185ff1c102d06111", "metadata": { "ExecuteTime": { - "end_time": "2024-09-20T14:02:50.286828Z", - "start_time": "2024-09-20T14:02:50.284369Z" + "end_time": "2024-12-24T11:53:59.961140Z", + "start_time": "2024-12-24T11:53:59.959103Z" } }, - "outputs": [], "source": [ "job_3 = \"\"\"\n", "CV 3: Relevant\n", @@ -433,19 +431,19 @@ "Statistical Analysis: SAS, SPSS\n", "Cloud Platforms: AWS, Azure\n", "\"\"\"" - ] + ], + "outputs": [], + "execution_count": 4 }, { "cell_type": "code", - "execution_count": null, "id": "d55ce4c58f8efb67", "metadata": { "ExecuteTime": { - "end_time": "2024-09-20T14:02:50.950343Z", - "start_time": "2024-09-20T14:02:50.946378Z" + "end_time": "2024-12-24T11:54:00.656495Z", + "start_time": "2024-12-24T11:54:00.654716Z" } }, - "outputs": [], "source": [ "job_4 = \"\"\"\n", "CV 4: Not Relevant\n", @@ -475,19 +473,19 @@ "Web Design: HTML, CSS\n", "Specialties: Branding and Identity, Typography\n", "\"\"\"" - ] + ], + "outputs": [], + "execution_count": 5 }, { "cell_type": "code", - "execution_count": null, "id": "ca4ecc32721ad332", "metadata": { "ExecuteTime": { - "end_time": "2024-09-20T14:02:51.548191Z", - "start_time": "2024-09-20T14:02:51.545520Z" + "end_time": "2024-12-24T11:54:01.184899Z", + "start_time": "2024-12-24T11:54:01.183028Z" } }, - "outputs": [], "source": [ "job_5 = \"\"\"\n", "CV 5: Not Relevant\n", @@ -517,7 +515,9 @@ "CRM Software: Salesforce, Zoho\n", "Negotiation and Relationship Building\n", "\"\"\"" - ] + ], + "outputs": [], + "execution_count": 6 }, { "cell_type": "markdown", @@ -529,10 +529,13 @@ }, { "cell_type": "code", - "execution_count": null, "id": "bce39dc6", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-24T11:54:04.417315Z", + "start_time": "2024-12-24T11:54:04.414132Z" + } + }, "source": [ "import os\n", "\n", @@ -570,14 +573,19 @@ "# os.environ[\"DB_PORT\"]=\"5432\"\n", "# os.environ[\"DB_USERNAME\"]=\"cognee\"\n", "# os.environ[\"DB_PASSWORD\"]=\"cognee\"" - ] + ], + "outputs": [], + "execution_count": 7 }, { "cell_type": "code", - "execution_count": null, "id": "9f1a1dbd", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-24T11:54:16.672999Z", + "start_time": "2024-12-24T11:54:07.425202Z" + } + }, "source": [ "# Reset the cognee system with the following command:\n", "\n", @@ -585,7 +593,32 @@ "\n", "await cognee.prune.prune_data()\n", "await cognee.prune.prune_system(metadata=True)" - ] + ], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "File /Users/vasilije/cognee/cognee/.cognee_system/databases/cognee_graph.pkl not found. Initializing an empty graph." + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Database deleted successfully.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/vasilije/cognee/.venv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + } + ], + "execution_count": 8 }, { "cell_type": "markdown", @@ -597,20 +630,48 @@ }, { "cell_type": "code", - "execution_count": null, "id": "904df61ba484a8e5", "metadata": { "ExecuteTime": { - "end_time": "2024-09-20T14:02:54.243987Z", - "start_time": "2024-09-20T14:02:52.498195Z" + "end_time": "2024-12-24T11:54:28.313862Z", + "start_time": "2024-12-24T11:54:23.756587Z" } }, - "outputs": [], "source": [ "import cognee\n", "\n", "await cognee.add([job_1, job_2, job_3, job_4, job_5, job_position], \"example\")" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User df77f15b-a077-4c86-a3e4-c059bf4cacb9 has registered.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/vasilije/cognee/.venv/lib/python3.11/site-packages/dlt/destinations/impl/sqlalchemy/merge_job.py:194: SAWarning: Table 'file_metadata' already exists within the given MetaData - not copying.\n", + " staging_table_obj = table_obj.to_metadata(\n", + "/Users/vasilije/cognee/.venv/lib/python3.11/site-packages/dlt/destinations/impl/sqlalchemy/merge_job.py:229: SAWarning: implicitly coercing SELECT object to scalar subquery; please use the .scalar_subquery() method to produce a scalar subquery.\n", + " order_by=order_dir_func(order_by_col),\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pipeline file_load_from_filesystem load step completed in 0.03 seconds\n", + "1 load package(s) were loaded to destination sqlalchemy and into dataset main\n", + "The sqlalchemy destination used sqlite:////Users/vasilije/cognee/cognee/.cognee_system/databases/cognee_db location to store data\n", + "Load package 1735041267.4777632 is LOADED and contains no failed jobs\n" + ] + } + ], + "execution_count": 9 }, { "cell_type": "markdown", @@ -622,15 +683,13 @@ }, { "cell_type": "code", - "execution_count": null, "id": "7c431fdef4921ae0", "metadata": { "ExecuteTime": { - "end_time": "2024-09-20T14:02:57.925667Z", - "start_time": "2024-09-20T14:02:57.922353Z" + "end_time": "2024-12-24T11:54:44.728010Z", + "start_time": "2024-12-24T11:54:44.723877Z" } }, - "outputs": [], "source": [ "from cognee.shared.data_models import KnowledgeGraph\n", "from cognee.modules.data.models import Dataset, Data\n", @@ -669,19 +728,19 @@ " print(result)\n", " except Exception as error:\n", " raise error\n" - ] + ], + "outputs": [], + "execution_count": 10 }, { "cell_type": "code", - "execution_count": null, "id": "f0a91b99c6215e09", "metadata": { "ExecuteTime": { - "end_time": "2024-09-20T14:02:58.905774Z", - "start_time": "2024-09-20T14:02:58.625915Z" + "end_time": "2024-12-24T11:55:26.027386Z", + "start_time": "2024-12-24T11:54:47.384342Z" } }, - "outputs": [], "source": [ "from cognee.modules.users.methods import get_default_user\n", "from cognee.modules.data.methods import get_datasets_by_name\n", @@ -691,7 +750,27 @@ "datasets = await get_datasets_by_name([\"example\"], user.id)\n", "\n", "await run_cognify_pipeline(datasets[0], user)" - ] + ], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "File /Users/vasilije/cognee/cognee/.cognee_system/databases/cognee_graph.pkl not found. Initializing an empty graph./Users/vasilije/cognee/.venv/lib/python3.11/site-packages/pydantic/main.py:1522: RuntimeWarning: fields may not start with an underscore, ignoring \"_metadata\"\n", + " warnings.warn(f'fields may not start with an underscore, ignoring \"{f_name}\"', RuntimeWarning)\n", + "/Users/vasilije/cognee/.venv/lib/python3.11/site-packages/pydantic/main.py:1522: RuntimeWarning: fields may not start with an underscore, ignoring \"__tablename__\"\n", + " warnings.warn(f'fields may not start with an underscore, ignoring \"{f_name}\"', RuntimeWarning)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[TextSummary(id=UUID('92b5d0a7-f980-529d-bb5b-48e72825a01a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='Experienced Senior Data Scientist with expertise in machine learning and predictive modeling, demonstrating over 8 years in the field.', made_from=DocumentChunk(id=UUID('70b823e2-5b12-57b5-ad8d-798e1d721f8e'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='\\nCV 1: Relevant\\nName: Dr. Emily Carter\\nContact Information:\\n\\nEmail: emily.carter@example.com\\nPhone: (555) 123-4567\\nSummary:\\n\\nSenior Data Scientist with over 8 years of experience in machine learning and predictive analytics. Expertise in developing advanced algorithms and deploying scalable models in production environments.\\n\\nEducation:\\n\\nPh.D. in Computer Science, Stanford University (2014)\\nB.S. in Mathematics, University of California, Berkeley (2010)\\nExperience:\\n\\nSenior Data Scientist, InnovateAI Labs (2016 – Present)\\nLed a team in developing machine learning models for natural language processing applications.\\nImplemented deep learning algorithms that improved prediction accuracy by 25%.\\nCollaborated with cross-functional teams to integrate models into cloud-based platforms.\\nData Scientist, DataWave Analytics (2014 – 2016)\\nDeveloped predictive models for customer segmentation and churn analysis.\\nAnalyzed large datasets using Hadoop and Spark frameworks.\\nSkills:\\n\\nProgramming Languages: Python, R, SQL\\nMachin e Learning: TensorFlow, Keras, Scikit-Learn\\nBig Data Technologies: Hadoop, Spark\\nData Visualization: Tableau, Matplotlib\\n', word_count=133, chunk_index=0, cut_type='sentence_cut', is_part_of=TextDocument(id=UUID('11a2b08a-c160-5961-80b7-b3498eafa973'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='text_85410f4ad1197f5974aef9aed6f103c8', raw_data_location='/Users/vasilije/cognee/cognee/.data_storage/data/text_85410f4ad1197f5974aef9aed6f103c8.txt', metadata_id=UUID('11a2b08a-c160-5961-80b7-b3498eafa973'), mime_type='text/plain', type='text'), contains=[Entity(id=UUID('29e771c8-4c3f-52de-9511-6b705878e130'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='dr. emily carter', is_a=EntityType(id=UUID('d072ba0f-e1a9-58bf-9974-e1802adc8134'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='person', description='person'), description='Senior Data Scientist with over 8 years of experience in machine learning and predictive analytics.'), Entity(id=UUID('ce8b394a-b30e-52fc-b80a-6352edc60e5b'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='stanford university', is_a=EntityType(id=UUID('d3d7b6b4-9b0d-52e8-9e09-a9e9cf4b5a4d'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='organization', description='organization'), description='Prestigious university located in Stanford, California.'), Entity(id=UUID('2c02c93c-9cd1-56b8-9cc0-55ff0b290e57'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='university of california, berkeley', is_a=EntityType(id=UUID('d3d7b6b4-9b0d-52e8-9e09-a9e9cf4b5a4d'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='organization', description='organization'), description='Public research university located in Berkeley, California.'), Entity(id=UUID('9780afb1-dccc-53eb-9a30-c0d4ce033711'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='innovateai labs', is_a=EntityType(id=UUID('d3d7b6b4-9b0d-52e8-9e09-a9e9cf4b5a4d'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='organization', description='organization'), description='A lab focused on artificial intelligence projects.'), Entity(id=UUID('50d0a685-5300-544f-b081-edca4b625886'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='datawave analytics', is_a=EntityType(id=UUID('d3d7b6b4-9b0d-52e8-9e09-a9e9cf4b5a4d'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='organization', description='organization'), description='Analytics firm specialized in data-driven insights.'), Entity(id=UUID('c95db510-e2ee-5a00-bded-20bbcb50c492'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='python', is_a=EntityType(id=UUID('80d409bb-e431-5939-a1ad-3acd96267128'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='programming language', description='programming language'), description='A high-level programming language used for general-purpose programming.'), Entity(id=UUID('39bd9707-8098-52ed-9cbf-bbdd26b963fb'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='r', is_a=EntityType(id=UUID('80d409bb-e431-5939-a1ad-3acd96267128'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='programming language', description='programming language'), description='A programming language and environment for statistical computing and graphics.'), Entity(id=UUID('1ff6821a-b207-5050-83e9-37ff67a27d03'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='sql', is_a=EntityType(id=UUID('80d409bb-e431-5939-a1ad-3acd96267128'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='programming language', description='programming language'), description='A domain-specific language used in programming and managing relational databases.'), Entity(id=UUID('6e72f6f5-0452-5d42-a4e8-4aba6a614cb1'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='tensorflow', is_a=EntityType(id=UUID('9ffe9ce7-8938-5a5c-8d03-5f1a4c5210a1'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='machine learning framework', description='machine learning framework'), description='An open-source software library for dataflow and differentiable programming across a range of tasks.'), Entity(id=UUID('ab85cdff-2a98-5c6d-99a3-df1f40f4ec16'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='keras', is_a=EntityType(id=UUID('9ffe9ce7-8938-5a5c-8d03-5f1a4c5210a1'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='machine learning framework', description='machine learning framework'), description='An open-source neural network library written in Python that runs on top of TensorFlow.'), Entity(id=UUID('37eecdcc-fb56-519c-bc18-d0d3afea0c0d'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='scikit-learn', is_a=EntityType(id=UUID('9ffe9ce7-8938-5a5c-8d03-5f1a4c5210a1'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='machine learning framework', description='machine learning framework'), description='A free software machine learning library for the Python programming language.'), Entity(id=UUID('f9a0eeca-c9ff-53b3-90eb-347254d7d7eb'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='hadoop', is_a=EntityType(id=UUID('7c2287d0-16fc-53dc-86ce-8d8e61c8642c'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='big data technology', description='big data technology'), description='An open-source framework for storing and processing large datasets in a distributed computing environment.'), Entity(id=UUID('46a235af-5ed5-5023-a4ec-c253e3f93031'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='spark', is_a=EntityType(id=UUID('7c2287d0-16fc-53dc-86ce-8d8e61c8642c'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='big data technology', description='big data technology'), description='An open-source unified analytics engine for large-scale data processing.'), Entity(id=UUID('c55004f3-8a6d-5130-b8bd-ed8278daa9a4'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='tableau', is_a=EntityType(id=UUID('674cc5fa-7849-575a-917f-90b7b77f52b3'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='data visualization software', description='data visualization software'), description='A visual analytics platform transforming the way we use data to solve problems.'), Entity(id=UUID('3c7adf8f-ef23-5330-a3fe-6a0b791cee2b'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='matplotlib', is_a=EntityType(id=UUID('3f3619fc-ebd1-50ed-adde-cf94e8bb3c1b'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='data visualization library', description='data visualization library'), description='A plotting library for the Python programming language and its numerical mathematics extension NumPy.')])), TextSummary(id=UUID('2f680bef-2edd-566e-b98c-78d549799e77'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='Senior Data Scientist specializing in Machine Learning at TechNova Solutions', made_from=DocumentChunk(id=UUID('eb6617b8-c78c-519b-b765-1eefc2e3a0d7'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='Senior Data Scientist (Machine Learning)\\n\\nCompany: TechNova Solutions\\nLocation: San Francisco, CA\\n\\nJob Description:\\n\\nTechNova Solutions is seeking a Senior Data Scientist specializing in Machine Learning to join our dynamic analytics team. The ideal candidate will have a strong background in developing and deploying machine learning models, working with large datasets, and translating complex data into actionable insights.\\n\\nResponsibilities:\\n\\nDevelop and implement advanced machine learning algorithms and models.\\nAnalyze large, complex datasets to extract meaningful patterns and insights.\\nCollaborate with cross-functional teams to integrate predictive models into products.\\nStay updated with the latest advancements in machine learning and data science.\\nMentor junior data scientists and provide technical guidance.\\nQualifications:\\n\\nMaster’s or Ph.D. in Data Science, Computer Science, Statistics, or a related field.\\n5+ years of experience in data science and machine learning.\\nProficient in Python, R, and SQL.\\nExpe rience with deep learning frameworks (e.g., TensorFlow, PyTorch).\\nStrong problem-solving skills and attention to detail.\\nCandidate CVs\\n', word_count=153, chunk_index=0, cut_type='sentence_cut', is_part_of=TextDocument(id=UUID('171f3035-4c37-5f7b-97c8-6b222404cc9a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='text_81a5a96a9a7325d40521ea453778ebe0', raw_data_location='/Users/vasilije/cognee/cognee/.data_storage/data/text_81a5a96a9a7325d40521ea453778ebe0.txt', metadata_id=UUID('171f3035-4c37-5f7b-97c8-6b222404cc9a'), mime_type='text/plain', type='text'), contains=[Entity(id=UUID('453a45c9-14e7-5b73-adb8-55991096fef0'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='technova solutions', is_a=EntityType(id=UUID('a6ed6bf1-fe31-5dfe-8ab4-484691fdf219'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='company', description='company'), description='A technology company specializing in data analytics and machine learning.'), Entity(id=UUID('435dbd37-ab20-503c-9e99-ab8b8a3484e5'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='senior data scientist', is_a=EntityType(id=UUID('524c6bbb-1534-5a51-8068-18dd4ae171eb'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='profession', description='profession'), description='A role focused on advanced data analysis and machine learning.'), Entity(id=UUID('198e2ab8-75e9-5931-97ab-da9a5a8e188c'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='san francisco, ca', is_a=EntityType(id=UUID('19dd7d4d-a966-5ed5-82a0-6ae377761a29'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='location', description='location'), description='A city in California, USA.'), Entity(id=UUID('5187986a-7305-5a63-b057-8f2c097419eb'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='machine learning', is_a=EntityType(id=UUID('0198571b-3e94-50ea-8b9f-19e3a31080c0'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='field', description='field'), description='A subset of artificial intelligence focused on the development of algorithms that enable computers to learn from data.'), Entity(id=UUID('d6545b21-153c-58ba-be47-46e5216521a3'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='data science', is_a=EntityType(id=UUID('0198571b-3e94-50ea-8b9f-19e3a31080c0'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='field', description='field'), description='A multidisciplinary field that uses scientific methods to extract knowledge and insights from data.'), Entity(id=UUID('c0d95499-de6b-5fcf-b0f5-9cbf427ad5c6'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='pytorch', is_a=EntityType(id=UUID('36a32bd3-8880-547a-949b-8447477d1ef5'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='framework', description='framework'), description='An open-source machine learning framework for deep learning.'), Entity(id=UUID('62b4dda1-de4a-5098-a56e-d3fe81f84dbc'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='masters or ph.d. in data science', is_a=EntityType(id=UUID('a49b283a-ce92-50e0-b7fa-ca7c628eb01a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='degree', description='degree'), description='Advanced academic degree in data science or related fields.')])), TextSummary(id=UUID('5c988618-db52-5979-9cf8-db80c0098285'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='Data Scientist with expertise in machine learning and statistical analysis, adept at managing extensive datasets and converting data into practical business solutions.', made_from=DocumentChunk(id=UUID('a6e82ac7-e791-5d6b-b4a9-f5e41cbe95bf'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='\\nCV 2: Relevant\\nName: Michael Rodriguez\\nContact Information:\\n\\nEmail: michael.rodriguez@example.com\\nPhone: (555) 234-5678\\nSummary:\\n\\nData Scientist with a strong background in machine learning and statistical modeling. Skilled in handling large datasets and translating data into actionable business insights.\\n\\nEducation:\\n\\nM.S. in Data Science, Carnegie Mellon University (2013)\\nB.S. in Computer Science, University of Michigan (2011)\\nExperience:\\n\\nSenior Data Scientist, Alpha Analytics (2017 – Present)\\nDeveloped machine learning models to optimize marketing strategies.\\nReduced customer acquisition cost by 15% through predictive modeling.\\nData Scientist, TechInsights (2013 – 2017)\\nAnalyzed user behavior data to improve product features.\\nImplemented A/B testing frameworks to evaluate product changes.\\nSkills:\\n\\nProgramming Languages: Python, Java, SQL\\nMachine Learning: Scikit-Learn, XGBoost\\nData Visualization: Seaborn, Plotly\\nDatabases: MySQL, MongoDB\\n', word_count=108, chunk_index=0, cut_type='sentence_cut', is_part_of=TextDocument(id=UUID('1f078b0a-3cc1-57a9-9802-f78565d49f29'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='text_f0f63a5c88dbbeef1eca23d32848220c', raw_data_location='/Users/vasilije/cognee/cognee/.data_storage/data/text_f0f63a5c88dbbeef1eca23d32848220c.txt', metadata_id=UUID('1f078b0a-3cc1-57a9-9802-f78565d49f29'), mime_type='text/plain', type='text'), contains=[Entity(id=UUID('73ae630f-7b09-5dce-8c18-45d0a57b30f9'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='michael rodriguez', is_a=EntityType(id=UUID('d072ba0f-e1a9-58bf-9974-e1802adc8134'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='person', description='person'), description='Data Scientist with a strong background in machine learning and statistical modeling.'), Entity(id=UUID('5534e0b0-d0c4-5ab9-82e9-91bed36f70bd'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='carnegie mellon university', is_a=EntityType(id=UUID('912b273c-683d-53ea-8ffe-aadef0b84237'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='educational institution', description='educational institution'), description='University known for its data science program.'), Entity(id=UUID('0af613e0-c11b-550d-ada2-2c2aa6550884'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='university of michigan', is_a=EntityType(id=UUID('912b273c-683d-53ea-8ffe-aadef0b84237'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='educational institution', description='educational institution'), description='University known for its computer science program.'), Entity(id=UUID('04a91fef-8a07-5d50-8f1b-46f3afeec497'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='alpha analytics', is_a=EntityType(id=UUID('a6ed6bf1-fe31-5dfe-8ab4-484691fdf219'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='company', description='company'), description='Company where Michael Rodriguez works as a Senior Data Scientist.'), Entity(id=UUID('3f848ed6-902f-5a8e-9577-cb67f8c17acd'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='techinsights', is_a=EntityType(id=UUID('a6ed6bf1-fe31-5dfe-8ab4-484691fdf219'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='company', description='company'), description='Company where Michael Rodriguez worked as a Data Scientist.')])), TextSummary(id=UUID('ee6cb607-27eb-5b87-bf2a-305721534263'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='Sales Manager with proven ability to enhance revenue and cultivate effective teams. Strong communicator and leader.', made_from=DocumentChunk(id=UUID('7e35407f-7c59-5429-8824-23f1d17118c0'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text=\"\\nCV 5: Not Relevant\\nName: Jessica Miller\\nContact Information:\\n\\nEmail: jessica.miller@example.com\\nPhone: (555) 567-8901\\nSummary:\\n\\nExperienced Sales Manager with a strong track record in driving sales growth and building high-performing teams. Excellent communication and leadership skills.\\n\\nEducation:\\n\\nB.A. in Business Administration, University of Southern California (2010)\\nExperience:\\n\\nSales Manager, Global Enterprises (2015 – Present)\\nManaged a sales team of 15 members, achieving a 20% increase in annual revenue.\\nDeveloped sales strategies that expanded customer base by 25%.\\nSales Representative, Market Leaders Inc. (2010 – 2015)\\nConsistently exceeded sales targets and received the 'Top Salesperson' award in 2013.\\nSkills:\\n\\nSales Strategy and Planning\\nTeam Leadership and Development\\nCRM Software: Salesforce, Zoho\\nNegotiation and Relationship Building\\n\", word_count=102, chunk_index=0, cut_type='sentence_cut', is_part_of=TextDocument(id=UUID('3c323fc9-9165-52da-a079-2627a9556b08'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='text_9b35c7df1f5d4dc84e78270c0bf9cac6', raw_data_location='/Users/vasilije/cognee/cognee/.data_storage/data/text_9b35c7df1f5d4dc84e78270c0bf9cac6.txt', metadata_id=UUID('3c323fc9-9165-52da-a079-2627a9556b08'), mime_type='text/plain', type='text'), contains=[Entity(id=UUID('36a5e3c8-c5f5-5ab5-8d59-ea69d8b36932'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='jessica miller', is_a=EntityType(id=UUID('d072ba0f-e1a9-58bf-9974-e1802adc8134'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='person', description='person'), description='An experienced sales manager with a strong track record in driving sales growth and building high-performing teams.'), Entity(id=UUID('5c32691d-c0e4-5378-9aab-dda8b0fa3931'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='global enterprises', is_a=EntityType(id=UUID('a6ed6bf1-fe31-5dfe-8ab4-484691fdf219'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='company', description='company'), description='A company where Jessica Miller worked as a Sales Manager.'), Entity(id=UUID('67544857-983a-5152-801d-4fc9d35d14e4'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='market leaders inc.', is_a=EntityType(id=UUID('a6ed6bf1-fe31-5dfe-8ab4-484691fdf219'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='company', description='company'), description='A company where Jessica Miller worked as a Sales Representative.'), Entity(id=UUID('f39d6c00-689b-5fd2-9021-893b28ac6ff2'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='university of southern california', is_a=EntityType(id=UUID('912b273c-683d-53ea-8ffe-aadef0b84237'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='educational institution', description='educational institution'), description='University where Jessica Miller obtained her degree in Business Administration.'), Entity(id=UUID('0abc801d-38ca-5003-b974-b60f1956c94a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='2010', is_a=EntityType(id=UUID('d61d99ac-b291-5666-9748-3e80e1c8b56a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='date', description='date'), description='Year Jessica Miller graduated from University of Southern California.'), Entity(id=UUID('7c8b43c1-e133-52e6-99aa-239534f1ed45'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='2015', is_a=EntityType(id=UUID('d61d99ac-b291-5666-9748-3e80e1c8b56a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='date', description='date'), description='Year Jessica Miller started working as Sales Manager at Global Enterprises.'), Entity(id=UUID('2f4749e9-e1e4-5af0-be80-2a10d07557ff'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='present', is_a=EntityType(id=UUID('d61d99ac-b291-5666-9748-3e80e1c8b56a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='date', description='date'), description=\"Current time indicative of Jessica Miller's ongoing role.\")])), TextSummary(id=UUID('d8a8668e-b122-5713-b289-932407bb294e'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='Creative Graphic Designer with 8+ years of expertise in visual design and branding, skilled in Adobe Creative Suite, dedicated to crafting engaging visuals.', made_from=DocumentChunk(id=UUID('c401b5b1-21d8-5830-8c7b-48e7d94c5b95'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='\\nCV 4: Not Relevant\\nName: David Thompson\\nContact Information:\\n\\nEmail: david.thompson@example.com\\nPhone: (555) 456-7890\\nSummary:\\n\\nCreative Graphic Designer with over 8 years of experience in visual design and branding. Proficient in Adobe Creative Suite and passionate about creating compelling visuals.\\n\\nEducation:\\n\\nB.F.A. in Graphic Design, Rhode Island School of Design (2012)\\nExperience:\\n\\nSenior Graphic Designer, CreativeWorks Agency (2015 – Present)\\nLed design projects for clients in various industries.\\nCreated branding materials that increased client engagement by 30%.\\nGraphic Designer, Visual Innovations (2012 – 2015)\\nDesigned marketing collateral, including brochures, logos, and websites.\\nCollaborated with the marketing team to develop cohesive brand strategies.\\nSkills:\\n\\nDesign Software: Adobe Photoshop, Illustrator, InDesign\\nWeb Design: HTML, CSS\\nSpecialties: Branding and Identity, Typography\\n', word_count=108, chunk_index=0, cut_type='sentence_cut', is_part_of=TextDocument(id=UUID('e71daf63-15a0-50fe-a909-766bc8fd311b'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='text_9abf20fa7defd7e49296c51b4e38edf2', raw_data_location='/Users/vasilije/cognee/cognee/.data_storage/data/text_9abf20fa7defd7e49296c51b4e38edf2.txt', metadata_id=UUID('e71daf63-15a0-50fe-a909-766bc8fd311b'), mime_type='text/plain', type='text'), contains=[Entity(id=UUID('a4777597-06c7-562c-bc44-56f74571a01a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='david thompson', is_a=EntityType(id=UUID('d072ba0f-e1a9-58bf-9974-e1802adc8134'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='person', description='person'), description='Creative Graphic Designer with over 8 years of experience in visual design and branding.'), Entity(id=UUID('ca20272a-3e88-552f-92fe-491e23f117f8'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='creativeworks agency', is_a=EntityType(id=UUID('d3d7b6b4-9b0d-52e8-9e09-a9e9cf4b5a4d'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='organization', description='organization'), description='An agency where David Thompson is a Senior Graphic Designer.'), Entity(id=UUID('1e97bb97-4d29-5fb8-863a-15ab51f1dd46'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='visual innovations', is_a=EntityType(id=UUID('d3d7b6b4-9b0d-52e8-9e09-a9e9cf4b5a4d'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='organization', description='organization'), description='An organization where David Thompson worked as a Graphic Designer.'), Entity(id=UUID('60b027fe-7bb4-535d-8a47-19f1a491591b'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='rhode island school of design', is_a=EntityType(id=UUID('b5866225-05ad-5cfc-908e-c22916c6a1c6'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='institution', description='institution'), description='Educational institution where David Thompson earned his B.F.A. in Graphic Design.'), Entity(id=UUID('7e3df89c-2691-580b-84dc-378cb1df3db6'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='adobe creative suite', is_a=EntityType(id=UUID('2d66edc2-1e14-55ab-8304-680b514a597a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='software', description='software'), description='A suite of graphic design, video editing, and web development applications.'), Entity(id=UUID('2a0f9b58-c695-5bad-baa2-fd2da02da013'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='html', is_a=EntityType(id=UUID('c90c7d6b-3532-5dcf-91e1-4a0e1f179794'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='language', description='language'), description='A markup language for creating web pages and applications.'), Entity(id=UUID('9b062f3c-fe02-5427-9b44-b287a1cac367'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='css', is_a=EntityType(id=UUID('c90c7d6b-3532-5dcf-91e1-4a0e1f179794'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='language', description='language'), description='A stylesheet language used for describing the presentation of a document written in HTML.')])), TextSummary(id=UUID('8aedca6b-fa78-5987-a79b-3b0bebff8eb1'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='Data Scientist with 6 years of expertise in machine learning, focused on utilizing data for business improvement and product enhancement.', made_from=DocumentChunk(id=UUID('00692e43-9f02-54d0-b695-44bf47342d36'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, text='\\nCV 3: Relevant\\nName: Sarah Nguyen\\nContact Information:\\n\\nEmail: sarah.nguyen@example.com\\nPhone: (555) 345-6789\\nSummary:\\n\\nData Scientist specializing in machine learning with 6 years of experience. Passionate about leveraging data to drive business solutions and improve product performance.\\n\\nEducation:\\n\\nM.S. in Statistics, University of Washington (2014)\\nB.S. in Applied Mathematics, University of Texas at Austin (2012)\\nExperience:\\n\\nData Scientist, QuantumTech (2016 – Present)\\nDesigned and implemented machine learning algorithms for financial forecasting.\\nImproved model efficiency by 20% through algorithm optimization.\\nJunior Data Scientist, DataCore Solutions (2014 – 2016)\\nAssisted in developing predictive models for supply chain optimization.\\nConducted data cleaning and preprocessing on large datasets.\\nSkills:\\n\\nProgramming Languages: Python, R\\nMachine Learning Frameworks: PyTorch, Scikit-Learn\\nStatistical Analysis: SAS, SPSS\\nCloud Platforms: AWS, Azure\\n', word_count=110, chunk_index=0, cut_type='sentence_cut', is_part_of=TextDocument(id=UUID('e7d6246b-e414-5b9d-8daa-6d4434b7fa47'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='text_f8c7482e727f228001b0046ed68d656f', raw_data_location='/Users/vasilije/cognee/cognee/.data_storage/data/text_f8c7482e727f228001b0046ed68d656f.txt', metadata_id=UUID('e7d6246b-e414-5b9d-8daa-6d4434b7fa47'), mime_type='text/plain', type='text'), contains=[Entity(id=UUID('4d8dda57-2681-5264-a2bd-e2ddfe66a785'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='sarah nguyen', is_a=EntityType(id=UUID('d072ba0f-e1a9-58bf-9974-e1802adc8134'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='person', description='person'), description='Data Scientist specializing in machine learning with 6 years of experience.'), Entity(id=UUID('ae74a35b-d5f1-5622-ade1-6703d5e069fb'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='university of washington', is_a=EntityType(id=UUID('912b273c-683d-53ea-8ffe-aadef0b84237'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='educational institution', description='educational institution'), description='University located in Seattle, Washington.'), Entity(id=UUID('301b3cf8-5a5c-585e-80bd-f79901e4368c'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='university of texas at austin', is_a=EntityType(id=UUID('912b273c-683d-53ea-8ffe-aadef0b84237'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='educational institution', description='educational institution'), description='Public research university located in Austin, Texas.'), Entity(id=UUID('0d980f2a-09dd-581e-acc3-cc2d87c1bab4'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='quantumtech', is_a=EntityType(id=UUID('a6ed6bf1-fe31-5dfe-8ab4-484691fdf219'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='company', description='company'), description='Company where Sarah Nguyen works as a Data Scientist from 2016 to present.'), Entity(id=UUID('95ac0551-38fc-5187-a422-533aeb7e8db0'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='datacore solutions', is_a=EntityType(id=UUID('a6ed6bf1-fe31-5dfe-8ab4-484691fdf219'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='company', description='company'), description='Company where Sarah Nguyen worked as a Junior Data Scientist from 2014 to 2016.'), Entity(id=UUID('3edcdf3f-25af-57a3-8878-8008bd7ea05a'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='aws', is_a=EntityType(id=UUID('d84d991a-dab3-5c36-8806-df076ccb731b'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='cloud platform', description='cloud platform'), description='Amazon Web Services, a cloud computing platform.'), Entity(id=UUID('8b431923-4aa2-5886-a661-b8de0f888a9b'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='azure', is_a=EntityType(id=UUID('d84d991a-dab3-5c36-8806-df076ccb731b'), updated_at=datetime.datetime(2024, 12, 24, 11, 54, 13, 481297, tzinfo=datetime.timezone.utc), topological_rank=0, name='cloud platform', description='cloud platform'), description='Microsoft Azure, a cloud computing service created by Microsoft.')]))]\n" + ] + } + ], + "execution_count": 11 }, { "cell_type": "markdown", @@ -703,23 +782,61 @@ }, { "cell_type": "code", - "execution_count": null, "id": "080389e5", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-29T16:55:51.378129Z", + "start_time": "2024-12-29T16:55:46.922951Z" + } + }, "source": [ "import os\n", "from cognee.shared.utils import render_graph\n", "from cognee.infrastructure.databases.graph import get_graph_engine\n", "import graphistry\n", - "\n", + "# from dotenv import load_dotenv \n", "graphistry.login(username=os.getenv(\"GRAPHISTRY_USERNAME\"), password=os.getenv(\"GRAPHISTRY_PASSWORD\"))\n", "\n", "graph_engine = await get_graph_engine()\n", "\n", "graph_url = await render_graph(graph_engine.graph)\n", "print(graph_url)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Graph is visualized at: https://hub.graphistry.com/graph/graph.html?dataset=cc21b1d2d6074323aa37af53e693b1a4&type=arrow&viztoken=db05565e-79e9-4fe3-99b2-b7a2e6d48eff&usertag=5f822e63-pygraphistry-0.33.9&splashAfter=1735491366&info=true\n", + "https://hub.graphistry.com/graph/graph.html?dataset=cc21b1d2d6074323aa37af53e693b1a4&type=arrow&viztoken=db05565e-79e9-4fe3-99b2-b7a2e6d48eff&usertag=5f822e63-pygraphistry-0.33.9&splashAfter=1735491366&info=true\n" + ] + } + ], + "execution_count": 12 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-29T16:56:06.571404Z", + "start_time": "2024-12-29T16:56:06.569280Z" + } + }, + "cell_type": "code", + "source": [ + "graph_engine = await get_graph_engine()\n", + "print(graph_url)" + ], + "id": "8f69caa0e353a889", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "https://hub.graphistry.com/graph/graph.html?dataset=cc21b1d2d6074323aa37af53e693b1a4&type=arrow&viztoken=db05565e-79e9-4fe3-99b2-b7a2e6d48eff&usertag=5f822e63-pygraphistry-0.33.9&splashAfter=1735491366&info=true\n" + ] + } + ], + "execution_count": 13 }, { "cell_type": "markdown", @@ -731,10 +848,13 @@ }, { "cell_type": "code", - "execution_count": null, "id": "e5e7dfc8", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-24T13:44:16.575843Z", + "start_time": "2024-12-24T13:44:16.047897Z" + } + }, "source": [ "async def search(\n", " vector_engine,\n", @@ -763,7 +883,26 @@ "results = await search(vector_engine, \"entity_name\", \"sarah.nguyen@example.com\")\n", "for result in results:\n", " print(result)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'id': '4d8dda57-2681-5264-a2bd-e2ddfe66a785', 'payload': {'id': '4d8dda57-2681-5264-a2bd-e2ddfe66a785', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'sarah nguyen'}, 'score': 0.5708460211753845}\n", + "{'id': '198e2ab8-75e9-5931-97ab-da9a5a8e188c', 'payload': {'id': '198e2ab8-75e9-5931-97ab-da9a5a8e188c', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'san francisco, ca'}, 'score': 1.349550724029541}\n", + "{'id': '435dbd37-ab20-503c-9e99-ab8b8a3484e5', 'payload': {'id': '435dbd37-ab20-503c-9e99-ab8b8a3484e5', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'senior data scientist'}, 'score': 1.3934645652770996}\n", + "{'id': '36a5e3c8-c5f5-5ab5-8d59-ea69d8b36932', 'payload': {'id': '36a5e3c8-c5f5-5ab5-8d59-ea69d8b36932', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'jessica miller'}, 'score': 1.4042469263076782}\n", + "{'id': '73ae630f-7b09-5dce-8c18-45d0a57b30f9', 'payload': {'id': '73ae630f-7b09-5dce-8c18-45d0a57b30f9', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'michael rodriguez'}, 'score': 1.4521806240081787}\n", + "{'id': '29e771c8-4c3f-52de-9511-6b705878e130', 'payload': {'id': '29e771c8-4c3f-52de-9511-6b705878e130', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'dr. emily carter'}, 'score': 1.471205472946167}\n", + "{'id': 'ce8b394a-b30e-52fc-b80a-6352edc60e5b', 'payload': {'id': 'ce8b394a-b30e-52fc-b80a-6352edc60e5b', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'stanford university'}, 'score': 1.4871069192886353}\n", + "{'id': '9780afb1-dccc-53eb-9a30-c0d4ce033711', 'payload': {'id': '9780afb1-dccc-53eb-9a30-c0d4ce033711', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'innovateai labs'}, 'score': 1.498255968093872}\n", + "{'id': '301b3cf8-5a5c-585e-80bd-f79901e4368c', 'payload': {'id': '301b3cf8-5a5c-585e-80bd-f79901e4368c', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'university of texas at austin'}, 'score': 1.5053001642227173}\n", + "{'id': '2c02c93c-9cd1-56b8-9cc0-55ff0b290e57', 'payload': {'id': '2c02c93c-9cd1-56b8-9cc0-55ff0b290e57', 'updated_at': datetime.datetime(2024, 12, 24, 11, 54, 13, 481297), 'topological_rank': 0, 'text': 'university of california, berkeley'}, 'score': 1.5213639736175537}\n" + ] + } + ], + "execution_count": 23 }, { "cell_type": "markdown", @@ -842,20 +981,455 @@ ] }, { - "cell_type": "markdown", - "id": "2ab3d84a", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-24T13:46:09.644509Z", + "start_time": "2024-12-24T13:46:04.538592Z" + } + }, + "cell_type": "code", "source": [ - "#### Bellow is a diagram of the cognee process for the data used in this example notebook" - ] + "!pip install wget\n", + "!pip install deepeval\n", + "!pip install ujson" + ], + "id": "afae18ac6a794925", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: wget in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (3.2)\r\n", + "\r\n", + "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m A new release of pip is available: \u001B[0m\u001B[31;49m23.2.1\u001B[0m\u001B[39;49m -> \u001B[0m\u001B[32;49m24.3.1\u001B[0m\r\n", + "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m To update, run: \u001B[0m\u001B[32;49mpip install --upgrade pip\u001B[0m\r\n", + "Requirement already satisfied: deepeval in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (2.0.9)\r\n", + "Requirement already satisfied: requests in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (2.32.3)\r\n", + "Requirement already satisfied: tqdm in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (4.67.1)\r\n", + "Requirement already satisfied: pytest in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (7.4.4)\r\n", + "Requirement already satisfied: tabulate in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (0.9.0)\r\n", + "Requirement already satisfied: typer in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (0.15.1)\r\n", + "Requirement already satisfied: rich in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (13.9.4)\r\n", + "Requirement already satisfied: protobuf in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (4.25.5)\r\n", + "Requirement already satisfied: pydantic in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (2.8.2)\r\n", + "Requirement already satisfied: sentry-sdk in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (2.19.2)\r\n", + "Requirement already satisfied: pytest-repeat in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (0.9.3)\r\n", + "Requirement already satisfied: pytest-xdist in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (3.6.1)\r\n", + "Requirement already satisfied: portalocker in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (3.0.0)\r\n", + "Requirement already satisfied: langchain in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (0.3.13)\r\n", + "Requirement already satisfied: langchain-core in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (0.3.28)\r\n", + "Requirement already satisfied: langchain_openai in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (0.2.14)\r\n", + "Requirement already satisfied: langchain-community in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (0.3.13)\r\n", + "Requirement already satisfied: docx2txt~=0.8 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (0.8)\r\n", + "Requirement already satisfied: importlib-metadata>=6.0.2 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (8.4.0)\r\n", + "Requirement already satisfied: tenacity<=9.0.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (8.5.0)\r\n", + "Requirement already satisfied: opentelemetry-api<2.0.0,>=1.24.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (1.27.0)\r\n", + "Requirement already satisfied: opentelemetry-sdk<2.0.0,>=1.24.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (1.27.0)\r\n", + "Requirement already satisfied: opentelemetry-exporter-otlp-proto-grpc<2.0.0,>=1.24.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (1.27.0)\r\n", + "Requirement already satisfied: grpcio==1.60.1 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (1.60.1)\r\n", + "Requirement already satisfied: nest-asyncio in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deepeval) (1.6.0)\r\n", + "Requirement already satisfied: zipp>=0.5 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from importlib-metadata>=6.0.2->deepeval) (3.21.0)\r\n", + "Requirement already satisfied: deprecated>=1.2.6 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from opentelemetry-api<2.0.0,>=1.24.0->deepeval) (1.2.15)\r\n", + "Requirement already satisfied: googleapis-common-protos~=1.52 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from opentelemetry-exporter-otlp-proto-grpc<2.0.0,>=1.24.0->deepeval) (1.66.0)\r\n", + "Requirement already satisfied: opentelemetry-exporter-otlp-proto-common==1.27.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from opentelemetry-exporter-otlp-proto-grpc<2.0.0,>=1.24.0->deepeval) (1.27.0)\r\n", + "Requirement already satisfied: opentelemetry-proto==1.27.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from opentelemetry-exporter-otlp-proto-grpc<2.0.0,>=1.24.0->deepeval) (1.27.0)\r\n", + "Requirement already satisfied: opentelemetry-semantic-conventions==0.48b0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from opentelemetry-sdk<2.0.0,>=1.24.0->deepeval) (0.48b0)\r\n", + "Requirement already satisfied: typing-extensions>=3.7.4 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from opentelemetry-sdk<2.0.0,>=1.24.0->deepeval) (4.12.2)\r\n", + "Requirement already satisfied: PyYAML>=5.3 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain->deepeval) (6.0.2)\r\n", + "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain->deepeval) (2.0.35)\r\n", + "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain->deepeval) (3.10.10)\r\n", + "Requirement already satisfied: langchain-text-splitters<0.4.0,>=0.3.3 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain->deepeval) (0.3.4)\r\n", + "Requirement already satisfied: langsmith<0.3,>=0.1.17 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain->deepeval) (0.2.4)\r\n", + "Requirement already satisfied: numpy<2,>=1.22.4 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain->deepeval) (1.26.4)\r\n", + "Requirement already satisfied: jsonpatch<2.0,>=1.33 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain-core->deepeval) (1.33)\r\n", + "Requirement already satisfied: packaging<25,>=23.2 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain-core->deepeval) (24.2)\r\n", + "Requirement already satisfied: annotated-types>=0.4.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from pydantic->deepeval) (0.7.0)\r\n", + "Requirement already satisfied: pydantic-core==2.20.1 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from pydantic->deepeval) (2.20.1)\r\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from requests->deepeval) (3.4.0)\r\n", + "Requirement already satisfied: idna<4,>=2.5 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from requests->deepeval) (3.10)\r\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from requests->deepeval) (2.2.3)\r\n", + "Requirement already satisfied: certifi>=2017.4.17 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from requests->deepeval) (2024.12.14)\r\n", + "Requirement already satisfied: dataclasses-json<0.7,>=0.5.7 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain-community->deepeval) (0.6.7)\r\n", + "Requirement already satisfied: httpx-sse<0.5.0,>=0.4.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain-community->deepeval) (0.4.0)\r\n", + "Requirement already satisfied: pydantic-settings<3.0.0,>=2.4.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain-community->deepeval) (2.7.0)\r\n", + "Requirement already satisfied: openai<2.0.0,>=1.58.1 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain_openai->deepeval) (1.58.1)\r\n", + "Requirement already satisfied: tiktoken<1,>=0.7 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langchain_openai->deepeval) (0.7.0)\r\n", + "Requirement already satisfied: iniconfig in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from pytest->deepeval) (2.0.0)\r\n", + "Requirement already satisfied: pluggy<2.0,>=0.12 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from pytest->deepeval) (1.5.0)\r\n", + "Requirement already satisfied: execnet>=2.1 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from pytest-xdist->deepeval) (2.1.1)\r\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from rich->deepeval) (3.0.0)\r\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from rich->deepeval) (2.18.0)\r\n", + "Requirement already satisfied: click>=8.0.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from typer->deepeval) (8.1.7)\r\n", + "Requirement already satisfied: shellingham>=1.3.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from typer->deepeval) (1.5.4)\r\n", + "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain->deepeval) (2.4.4)\r\n", + "Requirement already satisfied: aiosignal>=1.1.2 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain->deepeval) (1.3.2)\r\n", + "Requirement already satisfied: attrs>=17.3.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain->deepeval) (24.3.0)\r\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain->deepeval) (1.5.0)\r\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain->deepeval) (6.1.0)\r\n", + "Requirement already satisfied: yarl<2.0,>=1.12.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain->deepeval) (1.18.3)\r\n", + "Requirement already satisfied: marshmallow<4.0.0,>=3.18.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from dataclasses-json<0.7,>=0.5.7->langchain-community->deepeval) (3.23.2)\r\n", + "Requirement already satisfied: typing-inspect<1,>=0.4.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from dataclasses-json<0.7,>=0.5.7->langchain-community->deepeval) (0.9.0)\r\n", + "Requirement already satisfied: wrapt<2,>=1.10 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from deprecated>=1.2.6->opentelemetry-api<2.0.0,>=1.24.0->deepeval) (1.17.0)\r\n", + "Requirement already satisfied: jsonpointer>=1.9 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from jsonpatch<2.0,>=1.33->langchain-core->deepeval) (3.0.0)\r\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langsmith<0.3,>=0.1.17->langchain->deepeval) (0.27.0)\r\n", + "Requirement already satisfied: orjson<4.0.0,>=3.9.14 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langsmith<0.3,>=0.1.17->langchain->deepeval) (3.10.12)\r\n", + "Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from langsmith<0.3,>=0.1.17->langchain->deepeval) (1.0.0)\r\n", + "Requirement already satisfied: mdurl~=0.1 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from markdown-it-py>=2.2.0->rich->deepeval) (0.1.2)\r\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from openai<2.0.0,>=1.58.1->langchain_openai->deepeval) (4.7.0)\r\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from openai<2.0.0,>=1.58.1->langchain_openai->deepeval) (1.9.0)\r\n", + "Requirement already satisfied: jiter<1,>=0.4.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from openai<2.0.0,>=1.58.1->langchain_openai->deepeval) (0.5.0)\r\n", + "Requirement already satisfied: sniffio in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from openai<2.0.0,>=1.58.1->langchain_openai->deepeval) (1.3.1)\r\n", + "Requirement already satisfied: python-dotenv>=0.21.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from pydantic-settings<3.0.0,>=2.4.0->langchain-community->deepeval) (1.0.1)\r\n", + "Requirement already satisfied: regex>=2022.1.18 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from tiktoken<1,>=0.7->langchain_openai->deepeval) (2024.11.6)\r\n", + "Requirement already satisfied: httpcore==1.* in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from httpx<1,>=0.23.0->langsmith<0.3,>=0.1.17->langchain->deepeval) (1.0.7)\r\n", + "Requirement already satisfied: h11<0.15,>=0.13 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->langsmith<0.3,>=0.1.17->langchain->deepeval) (0.14.0)\r\n", + "Requirement already satisfied: mypy-extensions>=0.3.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain-community->deepeval) (1.0.0)\r\n", + "Requirement already satisfied: propcache>=0.2.0 in /Users/vasilije/cognee/.venv/lib/python3.11/site-packages (from yarl<2.0,>=1.12.0->aiohttp<4.0.0,>=3.8.3->langchain->deepeval) (0.2.1)\r\n", + "\r\n", + "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m A new release of pip is available: \u001B[0m\u001B[31;49m23.2.1\u001B[0m\u001B[39;49m -> \u001B[0m\u001B[32;49m24.3.1\u001B[0m\r\n", + "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m To update, run: \u001B[0m\u001B[32;49mpip install --upgrade pip\u001B[0m\r\n", + "Collecting ujson\r\n", + " Obtaining dependency information for ujson from https://files.pythonhosted.org/packages/8d/9f/4731ef0671a0653e9f5ba18db7c4596d8ecbf80c7922dd5fe4150f1aea76/ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl.metadata\r\n", + " Downloading ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl.metadata (9.3 kB)\r\n", + "Downloading ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl (51 kB)\r\n", + "\u001B[2K \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m51.8/51.8 kB\u001B[0m \u001B[31m1.7 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n", + "\u001B[?25hInstalling collected packages: ujson\r\n", + "Successfully installed ujson-5.10.0\r\n", + "\r\n", + "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m A new release of pip is available: \u001B[0m\u001B[31;49m23.2.1\u001B[0m\u001B[39;49m -> \u001B[0m\u001B[32;49m24.3.1\u001B[0m\r\n", + "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m To update, run: \u001B[0m\u001B[32;49mpip install --upgrade pip\u001B[0m\r\n" + ] + } + ], + "execution_count": 29 }, { - "cell_type": "markdown", - "id": "31412c52", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-24T15:29:11.123483Z", + "start_time": "2024-12-24T15:29:11.120888Z" + } + }, + "cell_type": "code", "source": [ - "![cognee_final.drawio.png](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABGkAAAIXCAYAAAAv7AMgAABOcnRFWHRteGZpbGUAJTNDbXhmaWxlJTIwaG9zdCUzRCUyMmFwcC5kaWFncmFtcy5uZXQlMjIlMjBhZ2VudCUzRCUyMk1vemlsbGElMkY1LjAlMjAoWDExJTNCJTIwVWJ1bnR1JTNCJTIwTGludXglMjB4ODZfNjQlM0IlMjBydiUzQTEzMS4wKSUyMEdlY2tvJTJGMjAxMDAxMDElMjBGaXJlZm94JTJGMTMxLjAlMjIlMjB2ZXJzaW9uJTNEJTIyMjQuNy4xNyUyMiUzRSUwQSUyMCUyMCUzQ2RpYWdyYW0lMjBuYW1lJTNEJTIyUGFnZS0xJTIyJTIwaWQlM0QlMjJLM1NsbDFHNEIwRE92RFM2MTJ3ZSUyMiUzRSUwQSUyMCUyMCUyMCUyMCUzQ214R3JhcGhNb2RlbCUyMGR4JTNEJTIyMzk0NiUyMiUyMGR5JTNEJTIyOTY3JTIyJTIwZ3JpZCUzRCUyMjAlMjIlMjBncmlkU2l6ZSUzRCUyMjEwJTIyJTIwZ3VpZGVzJTNEJTIyMSUyMiUyMHRvb2x0aXBzJTNEJTIyMSUyMiUyMGNvbm5lY3QlM0QlMjIxJTIyJTIwYXJyb3dzJTNEJTIyMSUyMiUyMGZvbGQlM0QlMjIxJTIyJTIwcGFnZSUzRCUyMjElMjIlMjBwYWdlU2NhbGUlM0QlMjIxJTIyJTIwcGFnZVdpZHRoJTNEJTIyODUwJTIyJTIwcGFnZUhlaWdodCUzRCUyMjExMDAlMjIlMjBiYWNrZ3JvdW5kJTNEJTIyJTIzZmZmZmZmJTIyJTIwbWF0aCUzRCUyMjAlMjIlMjBzaGFkb3clM0QlMjIwJTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTNDcm9vdCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyMCUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMjAlMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTU2JTIyJTIwdmFsdWUlM0QlMjIlMjIlMjBzdHlsZSUzRCUyMmdyb3VwJTIyJTIwdmVydGV4JTNEJTIyMSUyMiUyMGNvbm5lY3RhYmxlJTNEJTIyMCUyMiUyMHBhcmVudCUzRCUyMjElMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjEzMCUyMiUyMHklM0QlMjI2MCUyMiUyMHdpZHRoJTNEJTIyMTEyOCUyMiUyMGhlaWdodCUzRCUyMjUzMy44MyUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTI2JTIyJTIwc3R5bGUlM0QlMjJyb3VuZGVkJTNEMCUzQm9ydGhvZ29uYWxMb29wJTNEMSUzQmpldHR5U2l6ZSUzRGF1dG8lM0JodG1sJTNEMSUzQmVudHJ5WCUzRDAlM0JlbnRyeVklM0QwLjA5NyUzQmVudHJ5RHglM0QwJTNCZW50cnlEeSUzRDAlM0JlbnRyeVBlcmltZXRlciUzRDAlM0IlMjIlMjBlZGdlJTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTU2JTIyJTIwc291cmNlJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMSUyMiUyMHRhcmdldCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTE1JTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhHZW9tZXRyeSUyMHJlbGF0aXZlJTNEJTIyMSUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteFBvaW50JTIweCUzRCUyMjMxMC44Nzg5MDc2NTM2MTEyNCUyMiUyMHklM0QlMjIzMDguNTQwNDg4MzYwNjcwNSUyMiUyMGFzJTNEJTIydGFyZ2V0UG9pbnQlMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteEdlb21ldHJ5JTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC0xJTIyJTIwdmFsdWUlM0QlMjJDViUyMDElMjIlMjBzdHlsZSUzRCUyMndoaXRlU3BhY2UlM0R3cmFwJTNCaHRtbCUzRDElM0JzaGFwZSUzRG14Z3JhcGguYmFzaWMuZG9jdW1lbnQlM0Jmb250RmFtaWx5JTNEdmlyZ2lsJTNCZm9udFNvdXJjZSUzRGh0dHBzJTI1M0ElMjUyRiUyNTJGZXhjYWxpZHJhdy5ueWMzLmNkbi5kaWdpdGFsb2NlYW5zcGFjZXMuY29tJTI1MkZmb250cyUyNTJGVmlyZ2lsLndvZmYyJTNCZm9udFNpemUlM0QxNSUzQmNvbnRhaW5lciUzRDAlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjc0LjgyMTcwMzE5Nzk4Nzc3JTIyJTIweSUzRCUyMjE0OC4wOTk0MzQ0MTMxMjE4MyUyMiUyMHdpZHRoJTNEJTIyOTAuNzkxMjMyNDgyOTMyMDklMjIlMjBoZWlnaHQlM0QlMjIxMjMuNDE2MTk1MzQ0MjY4MTklMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC0yOSUyMiUyMHN0eWxlJTNEJTIycm91bmRlZCUzRDAlM0JvcnRob2dvbmFsTG9vcCUzRDElM0JqZXR0eVNpemUlM0RhdXRvJTNCaHRtbCUzRDElM0JlbnRyeVglM0QwJTNCZW50cnlZJTNEMSUzQmVudHJ5RHglM0QwJTNCZW50cnlEeSUzRDAlM0IlMjIlMjBlZGdlJTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTU2JTIyJTIwc291cmNlJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMyUyMiUyMHRhcmdldCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTE1JTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhHZW9tZXRyeSUyMHJlbGF0aXZlJTNEJTIyMSUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTMlMjIlMjB2YWx1ZSUzRCUyMkNWJTIwMyUyMiUyMHN0eWxlJTNEJTIyd2hpdGVTcGFjZSUzRHdyYXAlM0JodG1sJTNEMSUzQnNoYXBlJTNEbXhncmFwaC5iYXNpYy5kb2N1bWVudCUzQmZvbnRGYW1pbHklM0R2aXJnaWwlM0Jmb250U291cmNlJTNEaHR0cHMlMjUzQSUyNTJGJTI1MkZleGNhbGlkcmF3Lm55YzMuY2RuLmRpZ2l0YWxvY2VhbnNwYWNlcy5jb20lMjUyRmZvbnRzJTI1MkZWaXJnaWwud29mZjIlM0Jmb250U2l6ZSUzRDE1JTNCY29udGFpbmVyJTNEMCUzQiUyMiUyMHZlcnRleCUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyMzguNTA1MjEwMjA0ODE0OTUlMjIlMjB5JTNEJTIyMzk0LjkzMTgyNTEwMTY1ODIlMjIlMjB3aWR0aCUzRCUyMjkwLjc5MTIzMjQ4MjkzMjA5JTIyJTIwaGVpZ2h0JTNEJTIyMTIzLjQxNjE5NTM0NDI2ODE5JTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMjclMjIlMjBzdHlsZSUzRCUyMnJvdW5kZWQlM0QwJTNCb3J0aG9nb25hbExvb3AlM0QxJTNCamV0dHlTaXplJTNEYXV0byUzQmh0bWwlM0QxJTNCZW50cnlYJTNELTAuMDE0JTNCZW50cnlZJTNEMC4zODklM0JlbnRyeUR4JTNEMCUzQmVudHJ5RHklM0QwJTNCZW50cnlQZXJpbWV0ZXIlM0QwJTNCJTIyJTIwZWRnZSUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUyMHNvdXJjZSUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTQlMjIlMjB0YXJnZXQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC0xNSUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjByZWxhdGl2ZSUzRCUyMjElMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC00JTIyJTIwdmFsdWUlM0QlMjJDViUyMDIlMjIlMjBzdHlsZSUzRCUyMndoaXRlU3BhY2UlM0R3cmFwJTNCaHRtbCUzRDElM0JzaGFwZSUzRG14Z3JhcGguYmFzaWMuZG9jdW1lbnQlM0Jmb250RmFtaWx5JTNEdmlyZ2lsJTNCZm9udFNvdXJjZSUzRGh0dHBzJTI1M0ElMjUyRiUyNTJGZXhjYWxpZHJhdy5ueWMzLmNkbi5kaWdpdGFsb2NlYW5zcGFjZXMuY29tJTI1MkZmb250cyUyNTJGVmlyZ2lsLndvZmYyJTNCZm9udFNpemUlM0QxNSUzQmNvbnRhaW5lciUzRDAlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjI5LjQyNjA4Njk1NjUyMTc0JTIyJTIweSUzRCUyMjIyMi4xNDkxNTE2MTk2ODI3MyUyMiUyMHdpZHRoJTNEJTIyOTAuNzkxMjMyNDgyOTMyMDklMjIlMjBoZWlnaHQlM0QlMjIxMjMuNDE2MTk1MzQ0MjY4MTklMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC0xMCUyMiUyMHZhbHVlJTNEJTIyJTIyJTIwc3R5bGUlM0QlMjJza2V0Y2glM0QwJTNCcG9pbnRlckV2ZW50cyUzRDElM0JzaGFkb3clM0QwJTNCZGFzaGVkJTNEMCUzQmh0bWwlM0QxJTNCc3Ryb2tlQ29sb3IlM0Rub25lJTNCbGFiZWxQb3NpdGlvbiUzRGNlbnRlciUzQnZlcnRpY2FsTGFiZWxQb3NpdGlvbiUzRGJvdHRvbSUzQnZlcnRpY2FsQWxpZ24lM0R0b3AlM0JhbGlnbiUzRGNlbnRlciUzQmZpbGxDb2xvciUzRCUyMzUwNTA1MCUzQnNoYXBlJTNEbXhncmFwaC5tc2NhZS5vbXMubG9nX3NlYXJjaCUzQmNvbnRhaW5lciUzRDAlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjg5NC44MzkwMzU4NjA1ODIlMjIlMjB5JTNEJTIyNDAxLjEwMjYzNDg2ODg3MTYlMjIlMjB3aWR0aCUzRCUyMjkwLjc5MTIzMjQ4MjkzMjA5JTIyJTIwaGVpZ2h0JTNEJTIyMTExLjA3NDU3NTgwOTg0MTM4JTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMTElMjIlMjB2YWx1ZSUzRCUyMlByb2Nlc3NlZCUyMGRhdGElMjIlMjBzdHlsZSUzRCUyMnNoYXBlJTNEZGF0YXN0b3JlJTNCd2hpdGVTcGFjZSUzRHdyYXAlM0JodG1sJTNEMSUzQmZvbnRGYW1pbHklM0R2aXJnaWwlM0Jmb250U291cmNlJTNEaHR0cHMlMjUzQSUyNTJGJTI1MkZleGNhbGlkcmF3Lm55YzMuY2RuLmRpZ2l0YWxvY2VhbnNwYWNlcy5jb20lMjUyRmZvbnRzJTI1MkZWaXJnaWwud29mZjIlM0Jmb250U2l6ZSUzRDE2JTNCY29udGFpbmVyJTNEMCUzQiUyMiUyMHZlcnRleCUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyNzQ2LjY3NjgyMzU3MTY4NTMlMjIlMjB5JTNEJTIyMjc3LjY4NjQzOTUyNDYwMzQzJTIyJTIwd2lkdGglM0QlMjI5MC43OTEyMzI0ODI5MzIwOSUyMiUyMGhlaWdodCUzRCUyMjExMS4wNzQ1NzU4MDk4NDEzOCUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTI4JTIyJTIwc3R5bGUlM0QlMjJyb3VuZGVkJTNEMCUzQm9ydGhvZ29uYWxMb29wJTNEMSUzQmpldHR5U2l6ZSUzRGF1dG8lM0JodG1sJTNEMSUzQmVudHJ5WCUzRDAlM0JlbnRyeVklM0QwLjc1JTNCZW50cnlEeCUzRDAlM0JlbnRyeUR5JTNEMCUzQiUyMiUyMGVkZ2UlM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlMjBzb3VyY2UlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC0yJTIyJTIwdGFyZ2V0JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMTUlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIwcmVsYXRpdmUlM0QlMjIxJTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMiUyMiUyMHZhbHVlJTNEJTIySm9iJTIwcG9zaXRpb24lMjIlMjBzdHlsZSUzRCUyMndoaXRlU3BhY2UlM0R3cmFwJTNCaHRtbCUzRDElM0JzaGFwZSUzRG14Z3JhcGguYmFzaWMuZG9jdW1lbnQlM0Jmb250RmFtaWx5JTNEdmlyZ2lsJTNCZm9udFNvdXJjZSUzRGh0dHBzJTI1M0ElMjUyRiUyNTJGZXhjYWxpZHJhdy5ueWMzLmNkbi5kaWdpdGFsb2NlYW5zcGFjZXMuY29tJTI1MkZmb250cyUyNTJGVmlyZ2lsLndvZmYyJTNCZm9udFNpemUlM0QxNSUzQmNvbnRhaW5lciUzRDAlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjgzLjkwMDgyNjQ0NjI4MDk5JTIyJTIweSUzRCUyMjI5Ni4xOTg4Njg4MjYyNDM2NSUyMiUyMHdpZHRoJTNEJTIyOTAuNzkxMjMyNDgyOTMyMDklMjIlMjBoZWlnaHQlM0QlMjIxMjMuNDE2MTk1MzQ0MjY4MTklMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC0zMCUyMiUyMHN0eWxlJTNEJTIyZWRnZVN0eWxlJTNEb3J0aG9nb25hbEVkZ2VTdHlsZSUzQnJvdW5kZWQlM0QwJTNCb3J0aG9nb25hbExvb3AlM0QxJTNCamV0dHlTaXplJTNEYXV0byUzQmh0bWwlM0QxJTNCJTIyJTIwZWRnZSUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUyMHNvdXJjZSUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTE1JTIyJTIwdGFyZ2V0JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIwcmVsYXRpdmUlM0QlMjIxJTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMTUlMjIlMjB2YWx1ZSUzRCUyMkNvZ25lZSUyMEFkZCUyMiUyMHN0eWxlJTNEJTIycm91bmRlZCUzRDElM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQmh0bWwlM0QxJTNCZm9udEZhbWlseSUzRHZpcmdpbCUzQmZvbnRTb3VyY2UlM0RodHRwcyUyNTNBJTI1MkYlMjUyRmV4Y2FsaWRyYXcubnljMy5jZG4uZGlnaXRhbG9jZWFuc3BhY2VzLmNvbSUyNTJGZm9udHMlMjUyRlZpcmdpbC53b2ZmMiUzQmZvbnRTaXplJTNEMTYlM0Jjb250YWluZXIlM0QwJTNCJTIyJTIwdmVydGV4JTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTU2JTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhHZW9tZXRyeSUyMHglM0QlMjIzMDEuNzk5Nzg0NDA1MzE4JTIyJTIweSUzRCUyMjI5Ni4xOTg4Njg4MjYyNDM2NSUyMiUyMHdpZHRoJTNEJTIyMTA4Ljk0OTQ3ODk3OTUxODUlMjIlMjBoZWlnaHQlM0QlMjI3NC4wNDk3MTcyMDY1NjA5MSUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTMxJTIyJTIwc3R5bGUlM0QlMjJlZGdlU3R5bGUlM0RvcnRob2dvbmFsRWRnZVN0eWxlJTNCcm91bmRlZCUzRDAlM0JvcnRob2dvbmFsTG9vcCUzRDElM0JqZXR0eVNpemUlM0RhdXRvJTNCaHRtbCUzRDElM0JlbnRyeVglM0QwJTNCZW50cnlZJTNEMC41JTNCZW50cnlEeCUzRDAlM0JlbnRyeUR5JTNEMCUzQiUyMiUyMGVkZ2UlM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlMjBzb3VyY2UlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC0xNiUyMiUyMHRhcmdldCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTExJTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhHZW9tZXRyeSUyMHJlbGF0aXZlJTNEJTIyMSUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTE2JTIyJTIwdmFsdWUlM0QlMjJDb2duaWZ5JTIwcGlwZWxpbmUlMjIlMjBzdHlsZSUzRCUyMnJvdW5kZWQlM0QxJTNCd2hpdGVTcGFjZSUzRHdyYXAlM0JodG1sJTNEMSUzQmZvbnRGYW1pbHklM0R2aXJnaWwlM0Jmb250U291cmNlJTNEaHR0cHMlMjUzQSUyNTJGJTI1MkZleGNhbGlkcmF3Lm55YzMuY2RuLmRpZ2l0YWxvY2VhbnNwYWNlcy5jb20lMjUyRmZvbnRzJTI1MkZWaXJnaWwud29mZjIlM0Jmb250U2l6ZSUzRDE2JTNCY29udGFpbmVyJTNEMCUzQiUyMiUyMHZlcnRleCUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyNTE5LjY5ODc0MjM2NDM1NTElMjIlMjB5JTNEJTIyMjk2LjE5ODg2ODgyNjI0MzY1JTIyJTIwd2lkdGglM0QlMjIxMDguOTQ5NDc4OTc5NTE4NSUyMiUyMGhlaWdodCUzRCUyMjc0LjA0OTcxNzIwNjU2MDkxJTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMTglMjIlMjB2YWx1ZSUzRCUyMiUyMiUyMHN0eWxlJTNEJTIyaW1hZ2UlM0Jhc3BlY3QlM0RmaXhlZCUzQmh0bWwlM0QxJTNCcG9pbnRzJTNEJTVCJTVEJTNCYWxpZ24lM0RjZW50ZXIlM0Jmb250U2l6ZSUzRDEyJTNCaW1hZ2UlM0RpbWclMkZsaWIlMkZhenVyZTIlMkZnZW5lcmFsJTJGTWFuYWdlbWVudF9Hcm91cHMuc3ZnJTNCY29udGFpbmVyJTNEMCUzQiUyMiUyMHZlcnRleCUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyOTIwLjA4ODA3NzYxNDA4NTclMjIlMjB5JTNEJTIyMTYwLjQ0MTA1Mzk0NzU0ODY1JTIyJTIwd2lkdGglM0QlMjI2NC4xMDEyNTQzNzc5MDMyMiUyMiUyMGhlaWdodCUzRCUyMjYyLjE1NjYzOTUxMzEzNTEzJTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMTIlMjIlMjB2YWx1ZSUzRCUyMiUyMiUyMHN0eWxlJTNEJTIyc2hhcGUlM0RteGdyYXBoLnNpZ25zLmhlYWx0aGNhcmUuZXllJTNCaHRtbCUzRDElM0Jwb2ludGVyRXZlbnRzJTNEMSUzQmZpbGxDb2xvciUzRCUyMzAwMDAwMCUzQnN0cm9rZUNvbG9yJTNEbm9uZSUzQnZlcnRpY2FsTGFiZWxQb3NpdGlvbiUzRGJvdHRvbSUzQnZlcnRpY2FsQWxpZ24lM0R0b3AlM0JhbGlnbiUzRGNlbnRlciUzQmNvbnRhaW5lciUzRDAlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjkzOC4yNDYzMjQxMTA2NzE5JTIyJTIweSUzRCUyMjExMS4wNzQ1NzU4MDk4NDEzNiUyMiUyMHdpZHRoJTNEJTIyODkuODgzMzIwMTU4MTAyNzglMjIlMjBoZWlnaHQlM0QlMjI2Ny44Nzg5MDc0MzkzNDc1JTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMTklMjIlMjB2YWx1ZSUzRCUyMlF1ZXJ5JTIwJTIyJTIwc3R5bGUlM0QlMjJ0ZXh0JTNCaHRtbCUzRDElM0JhbGlnbiUzRGNlbnRlciUzQnZlcnRpY2FsQWxpZ24lM0RtaWRkbGUlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQnJvdW5kZWQlM0QwJTNCZm9udEZhbWlseSUzRHZpcmdpbCUzQmZvbnRTb3VyY2UlM0RodHRwcyUyNTNBJTI1MkYlMjUyRmV4Y2FsaWRyYXcubnljMy5jZG4uZGlnaXRhbG9jZWFuc3BhY2VzLmNvbSUyNTJGZm9udHMlMjUyRlZpcmdpbC53b2ZmMiUzQmZvbnRTaXplJTNEMTYlM0Jjb250YWluZXIlM0QwJTNCJTIyJTIwdmVydGV4JTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTU2JTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhHZW9tZXRyeSUyMHglM0QlMjI5MzcuMzM4NDExNzg1ODQyNiUyMiUyMHklM0QlMjI0NzUuMTUyMzUyMDc1NDMyNSUyMiUyMHdpZHRoJTNEJTIyOTAuNzkxMjMyNDgyOTMyMDklMjIlMjBoZWlnaHQlM0QlMjIzNy4wMjQ4NTg2MDMyODA0NiUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTIwJTIyJTIwdmFsdWUlM0QlMjJBbmFseXplJTIwS25vd2xlZGdlJTIwZ3JhcGglMjIlMjBzdHlsZSUzRCUyMnRleHQlM0JodG1sJTNEMSUzQmFsaWduJTNEY2VudGVyJTNCdmVydGljYWxBbGlnbiUzRG1pZGRsZSUzQndoaXRlU3BhY2UlM0R3cmFwJTNCcm91bmRlZCUzRDAlM0Jmb250RmFtaWx5JTNEdmlyZ2lsJTNCZm9udFNvdXJjZSUzRGh0dHBzJTI1M0ElMjUyRiUyNTJGZXhjYWxpZHJhdy5ueWMzLmNkbi5kaWdpdGFsb2NlYW5zcGFjZXMuY29tJTI1MkZmb250cyUyNTJGVmlyZ2lsLndvZmYyJTNCZm9udFNpemUlM0QxNiUzQmNvbnRhaW5lciUzRDAlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjk5MS44MTMxNTEyNzU2MDIlMjIlMjB5JTNEJTIyMTcyLjc4MjY3MzQ4MTk3NTQ1JTIyJTIwd2lkdGglM0QlMjIxMzYuMTg2ODQ4NzI0Mzk4MTUlMjIlMjBoZWlnaHQlM0QlMjI5Mi41NjIxNDY1MDgyMDExNCUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTM0JTIyJTIwdmFsdWUlM0QlMjJHYXRoZXIlMjBkYXRhJTIyJTIwc3R5bGUlM0QlMjJ0ZXh0JTNCaHRtbCUzRDElM0JhbGlnbiUzRGNlbnRlciUzQnZlcnRpY2FsQWxpZ24lM0RtaWRkbGUlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQnJvdW5kZWQlM0QwJTNCZm9udEZhbWlseSUzRHZpcmdpbCUzQmZvbnRTb3VyY2UlM0RodHRwcyUyNTNBJTI1MkYlMjUyRmV4Y2FsaWRyYXcubnljMy5jZG4uZGlnaXRhbG9jZWFuc3BhY2VzLmNvbSUyNTJGZm9udHMlMjUyRlZpcmdpbC53b2ZmMiUzQmZvbnRTaXplJTNEMTglM0Jjb250YWluZXIlM0QwJTNCJTIyJTIwdmVydGV4JTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTU2JTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhHZW9tZXRyeSUyMHglM0QlMjI2NS43NDI1Nzk5NDk2OTQ1OSUyMiUyMHklM0QlMjIzNy4wMjQ4NTg2MDMyODA0NjQlMjIlMjB3aWR0aCUzRCUyMjEwOC45NDk0Nzg5Nzk1MTg1JTIyJTIwaGVpZ2h0JTNEJTIyNzQuMDQ5NzE3MjA2NTYwOTElMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC0zNSUyMiUyMHZhbHVlJTNEJTIyUnVuJTIwY29nbmlmeSUyMHBpcGVsaW5lJTIyJTIwc3R5bGUlM0QlMjJ0ZXh0JTNCaHRtbCUzRDElM0JhbGlnbiUzRGNlbnRlciUzQnZlcnRpY2FsQWxpZ24lM0RtaWRkbGUlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQnJvdW5kZWQlM0QwJTNCZm9udEZhbWlseSUzRHZpcmdpbCUzQmZvbnRTb3VyY2UlM0RodHRwcyUyNTNBJTI1MkYlMjUyRmV4Y2FsaWRyYXcubnljMy5jZG4uZGlnaXRhbG9jZWFuc3BhY2VzLmNvbSUyNTJGZm9udHMlMjUyRlZpcmdpbC53b2ZmMiUzQmZvbnRTaXplJTNEMTglM0Jjb250YWluZXIlM0QwJTNCJTIyJTIwdmVydGV4JTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTU2JTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhHZW9tZXRyeSUyMHglM0QlMjI1MDcuMzklMjIlMjB5JTNEJTIyMjklMjIlMjB3aWR0aCUzRCUyMjExMy4yMiUyMiUyMGhlaWdodCUzRCUyMjg2LjM5JTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMzYlMjIlMjB2YWx1ZSUzRCUyMlVzZSUyMHRoZSUyMHByb2Nlc3NlZCUyMGRhdGElMjIlMjBzdHlsZSUzRCUyMnRleHQlM0JodG1sJTNEMSUzQmFsaWduJTNEY2VudGVyJTNCdmVydGljYWxBbGlnbiUzRG1pZGRsZSUzQndoaXRlU3BhY2UlM0R3cmFwJTNCcm91bmRlZCUzRDAlM0Jmb250RmFtaWx5JTNEdmlyZ2lsJTNCZm9udFNvdXJjZSUzRGh0dHBzJTI1M0ElMjUyRiUyNTJGZXhjYWxpZHJhdy5ueWMzLmNkbi5kaWdpdGFsb2NlYW5zcGFjZXMuY29tJTI1MkZmb250cyUyNTJGVmlyZ2lsLndvZmYyJTNCZm9udFNpemUlM0QxOCUzQmNvbnRhaW5lciUzRDAlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjcwNi4zMTAwMDAwMDAwMDAxJTIyJTIweSUzRCUyMjQ0LjQzJTIyJTIwd2lkdGglM0QlMjIxNzEuNTIlMjIlMjBoZWlnaHQlM0QlMjI3MC45NiUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTM4JTIyJTIwdmFsdWUlM0QlMjJBZGQlMjBkYXRhJTIwdG8lMjBjb2duZWUlMjIlMjBzdHlsZSUzRCUyMnRleHQlM0JodG1sJTNEMSUzQmFsaWduJTNEY2VudGVyJTNCdmVydGljYWxBbGlnbiUzRG1pZGRsZSUzQndoaXRlU3BhY2UlM0R3cmFwJTNCcm91bmRlZCUzRDAlM0Jmb250RmFtaWx5JTNEdmlyZ2lsJTNCZm9udFNvdXJjZSUzRGh0dHBzJTI1M0ElMjUyRiUyNTJGZXhjYWxpZHJhdy5ueWMzLmNkbi5kaWdpdGFsb2NlYW5zcGFjZXMuY29tJTI1MkZmb250cyUyNTJGVmlyZ2lsLndvZmYyJTNCZm9udFNpemUlM0QxOCUzQmNvbnRhaW5lciUzRDAlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjI5Mi43MjA2NjExNTcwMjQ4NCUyMiUyMHklM0QlMjI0MC4xMTAyNjM0ODY4ODcxNiUyMiUyMHdpZHRoJTNEJTIyMTE4LjAyODYwMjIyNzgxMTcyJTIyJTIwaGVpZ2h0JTNEJTIyNjcuODc4OTA3NDM5MzQ3NSUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTM5JTIyJTIwdmFsdWUlM0QlMjJTdGVwJTIwMSUyMiUyMHN0eWxlJTNEJTIydGV4dCUzQmh0bWwlM0QxJTNCYWxpZ24lM0RjZW50ZXIlM0J2ZXJ0aWNhbEFsaWduJTNEbWlkZGxlJTNCd2hpdGVTcGFjZSUzRHdyYXAlM0Jyb3VuZGVkJTNEMCUzQmZvbnRGYW1pbHklM0R2aXJnaWwlM0Jmb250U291cmNlJTNEaHR0cHMlMjUzQSUyNTJGJTI1MkZleGNhbGlkcmF3Lm55YzMuY2RuLmRpZ2l0YWxvY2VhbnNwYWNlcy5jb20lMjUyRmZvbnRzJTI1MkZWaXJnaWwud29mZjIlM0Jmb250Q29sb3IlM0QlMjMxODg4N2MlM0Jmb250U3R5bGUlM0QxJTNCZm9udFNpemUlM0QxOCUzQmNvbnRhaW5lciUzRDAlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjcwLjI4MjE0MTU3Mzg0MTE3JTIyJTIwd2lkdGglM0QlMjI5OS44NzAzNTU3MzEyMjUzJTIyJTIwaGVpZ2h0JTNEJTIyNzQuMDQ5NzE3MjA2NTYwOTElMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC00MCUyMiUyMHZhbHVlJTNEJTIyU3RlcCUyMDIlMjIlMjBzdHlsZSUzRCUyMnRleHQlM0JodG1sJTNEMSUzQmFsaWduJTNEY2VudGVyJTNCdmVydGljYWxBbGlnbiUzRG1pZGRsZSUzQndoaXRlU3BhY2UlM0R3cmFwJTNCcm91bmRlZCUzRDAlM0Jmb250RmFtaWx5JTNEdmlyZ2lsJTNCZm9udFNvdXJjZSUzRGh0dHBzJTI1M0ElMjUyRiUyNTJGZXhjYWxpZHJhdy5ueWMzLmNkbi5kaWdpdGFsb2NlYW5zcGFjZXMuY29tJTI1MkZmb250cyUyNTJGVmlyZ2lsLndvZmYyJTNCZm9udENvbG9yJTNEJTIzMTg4ODdjJTNCZm9udFN0eWxlJTNEMSUzQmZvbnRTaXplJTNEMTglM0Jjb250YWluZXIlM0QwJTNCJTIyJTIwdmVydGV4JTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTU2JTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhHZW9tZXRyeSUyMHglM0QlMjIzMDEuNzk5Nzg0NDA1MzE4JTIyJTIwd2lkdGglM0QlMjI5OS44NzAzNTU3MzEyMjUzJTIyJTIwaGVpZ2h0JTNEJTIyNzQuMDQ5NzE3MjA2NTYwOTElMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC00MSUyMiUyMHZhbHVlJTNEJTIyU3RlcCUyMDMlMjIlMjBzdHlsZSUzRCUyMnRleHQlM0JodG1sJTNEMSUzQmFsaWduJTNEY2VudGVyJTNCdmVydGljYWxBbGlnbiUzRG1pZGRsZSUzQndoaXRlU3BhY2UlM0R3cmFwJTNCcm91bmRlZCUzRDAlM0Jmb250RmFtaWx5JTNEdmlyZ2lsJTNCZm9udFNvdXJjZSUzRGh0dHBzJTI1M0ElMjUyRiUyNTJGZXhjYWxpZHJhdy5ueWMzLmNkbi5kaWdpdGFsb2NlYW5zcGFjZXMuY29tJTI1MkZmb250cyUyNTJGVmlyZ2lsLndvZmYyJTNCZm9udENvbG9yJTNEJTIzMTg4ODdjJTNCZm9udFN0eWxlJTNEMSUzQmZvbnRTaXplJTNEMTglM0Jjb250YWluZXIlM0QwJTNCJTIyJTIwdmVydGV4JTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTU2JTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhHZW9tZXRyeSUyMHglM0QlMjI1MTQuMDY4NzQyMzY0MzU1MSUyMiUyMHdpZHRoJTNEJTIyOTkuODcwMzU1NzMxMjI1MyUyMiUyMGhlaWdodCUzRCUyMjc0LjA0OTcxNzIwNjU2MDkxJTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNDIlMjIlMjB2YWx1ZSUzRCUyMlN0ZXAlMjA0JTIyJTIwc3R5bGUlM0QlMjJ0ZXh0JTNCaHRtbCUzRDElM0JhbGlnbiUzRGNlbnRlciUzQnZlcnRpY2FsQWxpZ24lM0RtaWRkbGUlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQnJvdW5kZWQlM0QwJTNCZm9udEZhbWlseSUzRHZpcmdpbCUzQmZvbnRTb3VyY2UlM0RodHRwcyUyNTNBJTI1MkYlMjUyRmV4Y2FsaWRyYXcubnljMy5jZG4uZGlnaXRhbG9jZWFuc3BhY2VzLmNvbSUyNTJGZm9udHMlMjUyRlZpcmdpbC53b2ZmMiUzQmZvbnRDb2xvciUzRCUyMzE4ODg3YyUzQmZvbnRTdHlsZSUzRDElM0Jmb250U2l6ZSUzRDE4JTNCY29udGFpbmVyJTNEMCUzQiUyMiUyMHZlcnRleCUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyNzQyLjEzNzI2MTk0NzUzODYlMjIlMjB3aWR0aCUzRCUyMjk5Ljg3MDM1NTczMTIyNTMlMjIlMjBoZWlnaHQlM0QlMjI3NC4wNDk3MTcyMDY1NjA5MSUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTQ3JTIyJTIwc3R5bGUlM0QlMjJyb3VuZGVkJTNEMCUzQm9ydGhvZ29uYWxMb29wJTNEMSUzQmpldHR5U2l6ZSUzRGF1dG8lM0JodG1sJTNEMSUzQmV4aXRYJTNELTAuMDE1JTNCZXhpdFklM0QwLjklM0JleGl0RHglM0QwJTNCZXhpdER5JTNEMCUzQmV4aXRQZXJpbWV0ZXIlM0QwJTNCJTIyJTIwZWRnZSUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUyMHNvdXJjZSUzRCUyMnd0d2FkVktleS1taTRvOWMwVmJoLTE4JTIyJTIwdGFyZ2V0JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtMTElMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIwcmVsYXRpdmUlM0QlMjIxJTIyJTIwYXMlM0QlMjJnZW9tZXRyeSUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRm14Q2VsbCUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNDglMjIlMjBzdHlsZSUzRCUyMnJvdW5kZWQlM0QwJTNCb3J0aG9nb25hbExvb3AlM0QxJTNCamV0dHlTaXplJTNEYXV0byUzQmh0bWwlM0QxJTNCZXhpdFglM0QwLjMxJTNCZXhpdFklM0QwLjMzJTNCZXhpdER4JTNEMCUzQmV4aXREeSUzRDAlM0JleGl0UGVyaW1ldGVyJTNEMCUzQmVudHJ5WCUzRDAuMzElM0JlbnRyeVklM0QwLjMzJTNCZW50cnlEeCUzRDAlM0JlbnRyeUR5JTNEMCUzQmVudHJ5UGVyaW1ldGVyJTNEMCUzQiUyMiUyMGVkZ2UlM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyd3R3YWRWS2V5LW1pNG85YzBWYmgtNTYlMjIlMjBzb3VyY2UlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC0xMCUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjByZWxhdGl2ZSUzRCUyMjElMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhQb2ludCUyMHglM0QlMjI5MjQuMDgyODkxODQzMzM0NyUyMiUyMHklM0QlMjI0NzEuMTQxMzI1NzI2NzQzODQlMjIlMjBhcyUzRCUyMnNvdXJjZVBvaW50JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhQb2ludCUyMHglM0QlMjI4MzcuNDY4MDU2MDU0NjE3JTIyJTIweSUzRCUyMjM3MC4yNDg1ODYwMzI4MDQ4NCUyMiUyMGFzJTNEJTIydGFyZ2V0UG9pbnQlMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteEdlb21ldHJ5JTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01MyUyMiUyMHZhbHVlJTNEJTIyJTIyJTIwc3R5bGUlM0QlMjJyb3VuZGVkJTNEMCUzQndoaXRlU3BhY2UlM0R3cmFwJTNCaHRtbCUzRDElM0JzdHJva2VDb2xvciUzRCUyM0ZGRkZGRiUzQiUyMiUyMHZlcnRleCUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB5JTNEJTIyMjIyLjE0NjM1NzI5MDczMTU0JTIyJTIwd2lkdGglM0QlMjIyMy41NDA4Njk1NjUyMTczOTIlMjIlMjBoZWlnaHQlM0QlMjI1OC42ODA5MDc5NzUwMTA1MzQlMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NCUyMiUyMHZhbHVlJTNEJTIyJTIyJTIwc3R5bGUlM0QlMjJyb3VuZGVkJTNEMCUzQndoaXRlU3BhY2UlM0R3cmFwJTNCaHRtbCUzRDElM0JzdHJva2VDb2xvciUzRCUyM0ZGRkZGRiUzQiUyMiUyMHZlcnRleCUzRCUyMjElMjIlMjBwYXJlbnQlM0QlMjJ3dHdhZFZLZXktbWk0bzljMFZiaC01NiUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyMTM1LjM2JTIyJTIweSUzRCUyMjQ3NS4xNDkwOTIwMjQ5ODk1JTIyJTIwd2lkdGglM0QlMjIyMy41NDA4Njk1NjUyMTczOTIlMjIlMjBoZWlnaHQlM0QlMjI1OC42ODA5MDc5NzUwMTA1MzQlMjIlMjBhcyUzRCUyMmdlb21ldHJ5JTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGbXhDZWxsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGcm9vdCUzRSUwQSUyMCUyMCUyMCUyMCUzQyUyRm14R3JhcGhNb2RlbCUzRSUwQSUyMCUyMCUzQyUyRmRpYWdyYW0lM0UlMEElM0MlMkZteGZpbGUlM0UlMEHV7mQ/AAAgAElEQVR4XuydCbxV4/7/v81FKEpFkxShEipKVEilIhS6RTJdQ8h4zbNrHkMZy80QQiVKgxCVsZFGDZSiUJTm+q/387POf7fbw9rD2Xufcz7f++qWc571rGe91157Pc/n+Q7FtntmMhEQAREQAREQAREQAREQAREQAREQAREQgawSKCaRJqv8dXIREAEREAEREAEREAEREAEREAEREAERcAQk0uiDIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAI5QEAiTQ7cBA1BBERABERABERABERABERABERABERABCTS6DMgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAjlAQCJNDtwEDUEEREAEREAEREAEREAEREAEREAEREAEJNLoMyACIiACIiACIiACIiACIiACIiACIiACOUBAIk0O3AQNQQREQAREQAREQAREQAREQAREQAREQAQk0ugzIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAI5QEAiTQ7cBA1BBERABERABERABERABERABERABERABCTS6DMgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAjlAQCJNDtwEDUEEREAEREAEREAEREAEREAEREAEREAEJNLoMyACIiACIiACIiACIiACIiACIiACIiACOUBAIk0O3AQNQQREQAREQAREQAREQAREQAREQAREQAQk0ugzIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAI5QEAiTQ7cBA1BBERABERABERABERABERABERABERABCTS6DMgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAjlAQCJNDtwEDUEEREAEREAEREAEREAEREAEREAEREAEJNLoMyACIiACIiACIiACIiACIiACIiACIiACOUBAIk0O3AQNQQREQAREQAREQAREQAREQAREQAREQAQk0ugzIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAI5QEAiTQ7cBA1BBERABERABERABERABERABERABERABCTS6DMgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAjlAQCJNDtwEDUEEREAEREAEREAEREAEREAEREAEREAEJNLoMyACIiACIiACIiACIiACIiACIiACIiACOUBAIk0O3AQNQQREQAREQAREQAREQAREQAREQAREQAQk0ugzIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAI5QEAiTQ7cBA1BBERABERABERABERABERABERABERABCTS6DMgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAjlAQCJNDtyE0CGs3bTRxi6Yawt+X2Un1j3QDq26r/v15m1b7eWpX1rLmnWsfuUqOTPq7bbdPl280A6vVt12K1MmZ8algYhAOggUtOcx/Jq3bNtmJYsXTwcK9SECKRPg8/j5koU2dflS23/PSta5foO8PkfO/c7KlCxpbfc/MOXzpKODn9asttdnfGO/rvvL/tq40eruVdkO9t69rfara+VL612XDsbqI/sElv/1p41ZMMd+X/+3/avREVal/G5uUNNXLLNvf15qvQ8/MvuD1AhEQAREoAgSkEiTAzcdoeOLn5Z4E8JvbcScWbZhy2Y3qqq77W5TL73O/Xveql+t1Yv93ARxyBm9cmDU/zeEPiOH2tvfTbcnO55u3Ro0zplxaSAikCyBgvw8cs0rvEn3Q599ZB/M+95Wb1hvFcvtYkdWr2U3t2rrFpoyEcg0gR+8TYc3Z061ITO/9USPtXmnn3TRVbZfxT3dfzfod7+t37zZ5vS9yUoVL5HpIe5wPp6bIwc8an9u3LDTONiM6O4tZi878hjbe9fyWR2nTi4CyRDYtHWLfTh/jr3miZCfLPrBm4Fud92cdvCh9nTnru7fFw1/w97z5qPv9bzImuxbI5nT5Msxm7dutTdmTbWGVarlbWJyokV//G5nDBno5qHXH3N8vpxbnYqACIhAJglIpMkk7bBz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3fqMHLpDWcVCcWG6CBEogAT0PBbAm6Yh5ywB8mAc7uXRIM+Gci7l7G3SwIoQAfIWslHx3RU3KjywCN13XaoIiEDqBCTSpM6wQPWwzCuX3cQrm02M8beXXhc4iWKBukgNVgQKCAE9jwXkRmmYBYbAUc8+aku8/DHjvKqH5GCTiYAIZI/A1aPedUmL7zr+JLuwSfPsDURnFgEREIECRkAiTQG7YekY7kn/e9amepWe3vnX+dbcSy4sEwERyB4BPY/ZY68zFz4C5GV7+ouJroLUNV7FQpkIiED2CHy0cJ71eGuwl59mP1eVTSYCIiACIhCMgESaYJwKVSvKIlIe9Y42HVxWfpkIiED2COh5zB57nbnwEaDM/B0fjbarjm5tdSruVfguUFckAgWIAAUteB6PqV3H2u5/YAEauYYqAiIgAtklIJEmu/x1dhEQAREQAREQAREQAREQAREQAREQARFwBCTS6IMgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAjlAQCJNDtwEDUEEREAEREAERKDgE1i6dKlNmzbNmjZtalWqVCn4F6QrEAEREAEREAERyDgBiTQZR64TioAIiIAIiIAIFDYCK1eutIMOOsh+++03K1++vD300EN28cUXF7bL1PWIgAiIgAiIgAjkMwGJNPkMWN2LgAiIgAiIQEEg0L9/fxs9erRdffXVdtRRR1mZMmUKwrBzZoyPPvqoXXPNNXbsscfaunXr7JtvvrGbbrrJ7r333pwZowYiAiIgAiIgAiKQ+wQk0uT+PdIIRUAEIhDYuHGj+2m8heSSJUvsuOOOsyFDhrgQhGi2bds2a9mypfXs2dMuvfTSlJmz0P3111/tlVdeSbkvdSACmSCw55572h9//OFOVaxYMdtvv/2se/fuds4559gBBxyQiSEU6HOcd9559uGHH9rChQutZMmSdscdd9g999xjN954o/33v/8t0NemwYuACIiACIiACGSOgESazLHO6Jlwt959992tVKlSGT2vTiYCmSCwdetWO/DAA23XXXe16dOnxzzliy++aBdccIE9/PDDbpc7mv3www9Wt25d69Spk7333nspX0aNGjVs9erV9tdff6XU1/Lly61atWop9ZHswfoeSZZcwTtu1apV1qxZMzv00ENdyA7P2Pfff2/jxo2zDRs2WPPmze2qq66ybt26FbyLy4cRv/zyy/bl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TXBWaikCIiACIiACIiACIiACIiACIiACIiAC+UZAIk2+oVXHIiACIiACIiACIiACIiACIiACIiACIhCcgESa4KzUUgREQAREQAREQAREQAREQAREQAREQATyjYBEmnxDq45FQAREQAREQAREQAREQAREQAREQAREIDgBiTTBWamlCIiACIiACIiACIiACIiACIiACIiACOQbAYk0+YZWHYuACIiACIiACIiACIiACIiACIiACIhAcAISaYKzUksREAEREAEREAEREAEREAEREAEREAERyDcCEmnyDa06FgEREAEREAEREAEREAEREAEREAEREIHgBCTSBGelliIgAiIgAiIgAiIgAiIgAiIgAiLgCKxatcr9WbFihfuzYcMG9/OqVatahQoV3N/Vq1e3kiVLipgIBCYgkSYwKjUUAREQAREQAREQAREQAREQAREoigSmTZtm48aNs/Hjx9usWbNs6dKlgTGUL1/eateubS1btrR27dpZ69atnYgjE4FIBCTS6HMhAiIgAiIgAiIgAiIgAiIgAiIgAiEEFi9ebKNHj3aiDOLM6tWr08qncePG1r59e2vVqpWdcMIJ8rZJK92C3ZlEmoJ9/zR6ERABERABERABERABERABERCBNBAgdGnEiBE2fPhw93em7LDDDrNTTjnFTj75ZOPfsqJNQCJN0b7/unoREAEREAEREAEREAEREAERKNIE5s6dmyfMfP7551ljUaNGjTyxpm3btlkbh06cXQISabLLX2cXAREQAREQAREQAREQAREQARHIAoEtW7bY448/bo888ohL/JsrRqLhTp062d13320NGjTIlWFpHBkiIJEmQ6B1GhEQAREQAREQAREQAREQAREQgdwgMHDgQHvxxRctm54z8UhUrlzZzjvvPDv//POtXr168Zrr94WEgESaQnIjdRkiIAIiIAIiIAIiIAIiIAIiIAKxCXz55ZdOnOHP1q1bCwSuY4891ok1vXr1KhDj1SBTIyCRJjV+OloEREAEREAEREAEREAEREAERKAAEBg5cqRdfvnlRuWmIFapUiXr0qWLERY1aNCgIIcEbtOkSROrWrWqqyBF/0Hs3HPPtYceesgYl6zwEpBIU3jvra5MBERABERABERABERABERABIo8AUSQCy+8MJDQQj6Ys846y3mttG7d2u68806755578oXhxRdfbLfffrsNGTLEXn75ZZs2bVrc8yDQDB482JXvlhVOAhJpCud91VWJgAiIgAiIgAiIgAiIgAiIQJEnQELgbt262WeffRaTRdmyZZ04c/PNN1vdunWdd8vZZ5/tBJT8tK5du9rrr79uiEPDhg1zolA8sYa2/fr1M0QeWeEjIJGm8N1TXZEIiIAIiIAIiIAIiIAIiIAIFHkCc+bMsc6dO9uCBQuiskDwQOxAnCH8yLerrrrKVX7KhHH+/v37550KsebWW2+1WbNmxTz9tddea/fdd58TeGSFh4BEmsJzL3UlIiACIiACIiACIiACIiACIiACHgFyveBBs3bt2qg86tevb1R5Ouqoo3ZogziDSJNJQ2y54YYb8k6JJ8+9997rQq1i5ayhVDeeOOXLl8/kcHWufCQgkSYf4aprERABERABERABERABERABERCBzBIYMGCASxAcTdzA8+SWW25x3jPhXijjxo2zDh06BE7mm64rYxwTJkywli1b7tAl3jTk05kyZUrUUzVu3NhGjRq1gydQusalfjJPQCJN5pnrjCIgAiIgAiIgAiIgAiIgAiIgAvlAIJ4XDPlm3n33XWvQoMFOZ9+wYYM1bNgwZnhUPgw5r8vatWvbzJkzd/KKQWx6+OGHXQhUNOEJryBEntCQrfwcq/rOPwISafKPrXoWAREQAREQAREQAREQAREQARHIEAGS/Hbv3j3q2aiIRGhQhQoVIrYhtAghJJtGmW1CsCIZXj5c36pVqyL+Ho+aiRMnKvQpmzcwDeeWSJMGiOpCBERABERABERABERABERABEQgewTihSkR3kS562hJdhcvXmwHHXSQ4U2TiFWvXt1Vhdp///1dVSiMRMVz5851laGoLpWoTZ061RBcIhnjPPXUU6NWgCJcCo8aJRNOlHrutJdIkzv3QiMRAREQAREQAREQAREQAREQARFIkMDXX39tbdq0iZgkGLFi8ODBTkiJZQgfVFUKaogod999t+GdE00QITSJPkkAHK+sduh5u3Tp4kKyohnJkHv37m1Dhw6N2CS0rHfQ61G73CEgkSZ37oVGIgIiIAIiIAIiIAIiIAIiIAIikAABymwj0ETyWEE8IbwJ0SKWIfI0bdo08Fn79u3rSl+XLVs20DF451AtioTGQS2WNw19IAAR+hRNqAkv6x30vGqXfQISabJ/DzQCERABERABERABERABERABERCBBAmQm+WYY44xhJpwoyT1e++9Z61bt47b6yWXXBJYQMErp2fPnnH7jNQgXlLj0GM4B+eKZfGEGkK88PaRFSwCEmkK1v3SaEVABERABERABERABERABERABDwC0UKUEGgoSR1ezjoSNLxcKleuHDFUKrw9ggfCRypGYmISFMczvHRWrlwZNwlwLKEGT6KxY8cGEqrijUe/zxwBiTSZY60ziYAIiIAIiIAIiIAIiIAIiIAIpIEAoUN4wIQbwgT5XDp16hToLIMGDXL5XeJZrKpL8Y4N//3ZZ59tr7zyStzDnn/+ebvgggvitkOo6dChg5E8Odwo603oVLSKVnE7V4OME5BIk3HkOqEIiIAIiIAIiIAIiIAIiIAIiECyBAhvIocMCXTDrV+/ftanT5/AXTdv3tymTJkSs32lSpVs/vz5aRM6CNOqV6+erV69OuZ58QSipHYQoy+uJVLoV5DQqSDnUJvMEJBIkxnOOosIiIAIiIAIiIAIiIAIiIAIiECKBL7//nu77LLL7OOPP96pJ8KIbr755sBnmDRpkh199NFx2+Nt06tXrx3aIYaQcBghBW+VRO3hhx+26667LuZhlPeePHmy8XcQGz16tOtz1qxZOzTHu+jpp5+2iy66KEg3apNlAhJpsnwDdHoREAEREAEREAEREAEREAEREIFgBG688Ua7//77d2pMKWwSBUcrhx2pd8pjk9cmljVo0MBmzpyZ14R/Iwa9+eabeT+7/PLLjVwz5LYJaoQoHXbYYTsJKuHHv/XWW3GrU4UeQ8gToU/0H2qEOzH2oIJP0OtQu/QTkEiTfqbqUQREQAREQAREQAREQAREQAREIM0E8J5p27btTgIEwgMCRKJ5V4Ik8X3ooYfs2muvzbsS8tfgWRNuyZS8DuJNQ04actMkYohYiFnhRqWrCRMmJNKV2maBgESaLEDXKUVABERABERABERABERABERABIIToApTw4YNbcGCBTsdhPAQpNR2+IEIPpGS7frt8MpZvny5kZMGe+2116xHjx4RB73vvvs675oWLVoEvihy01SrVm0n0Sm0g/r169vs2bMD90lDvGi4tkghYUGTESd0QjVOKwGJNGnFqc5EQAREQAREQAREQAREQAREQATSTeDxxx+3q666aqdu8XLB2yUZq1ixYszkvVSIIoTKN6pJUVUqmt1www123333JTSUzp0728iRI2Mes3nz5oTCuOhs8eLFLpwqPDlx1apVbdGiRUaJb1luEpBIk5v3RaMSAREQAREQAREQAREQAREQARHwCCA07LfffjsJDo0bN7avvvoqYQEDqCTXxTMnlj322GPWt2/fvCZHHnmkffnll1EPSaZMd7TQpNCTEMpFbpxEbciQIda9e/edDrvjjjvs9ttvT7Q7tc8QAYk0GQKt04iACIiACIiACIiACIiACIiACCROIFKyYEKREC8IB0rGCAVq06ZNzEMRgJo0aZLXBqEID5VolkxoUpBxvPvuu9alS5dkLtO6detmQ4cO3eFYcvcQQoVXjSz3CEikyb17ohGJgAiIgAiIgAiIgAiIgAiIgAh4BJYuXWr16tUzctKEWiphTvQTzcvEP0f58uXtjz/+2MFL54gjjrBvv/026n3p2bOnDR48OKH7tnbtWiPsKrwaU2gnhFARSpWMrVixwvHjPKHWp08f69evXzJd6ph8JiCRJp8Bq3sREAEREAEREAEREAEREAEREIHkCCB8vPrqqzscfPzxx9vLL79sJOtN1hA+brrppqiHR6qEdMYZZxglsaPZbbfdZnfeeWfCQyIx8W+//Rb1OPLxXHnllQn36x9AaNNdd921w/E1atRwXI855pik+9WB+UNAIk3+cFWvIiACIiACIiACIiACIiACIiACKRCIVk2JEti9evVKoWez66+/PmbC4VNPPdXeeeedHc5BZaSLLroo4nkJIaK6E1WVEjU8XSJVrfL7uffee2MKSvHO98MPP9g555xjkyZN2qFpMmXD451Lv0+dgESa1BmqBxEQAREQAREQAREQAREQAREQgTQTiFT5KJVkwaHDu+666+zhhx+OOuLwyk5+w6ZNm9rXX3+903GRPG+C4iA3TqRy2f7xySQkDj83eWnITxNuU6dONZjKcoeARJrcuRcaiQiIgAiIgAiIgAiIgAiIgAiIgEeAik7kagm3gQMHGqJFqta7d2/DIyeaRRNGJk6caHfffbeNHTs279Czzz7bbrnlFjvggAOSGlYmRBoGFinxMdWrqGIlyx0CEmly515oJCIgAiIgAiIgAiIgAiIgAiIgAv8QiORJQylqvD+o7pSKRaoYFdpfNE8a2qxatcpVTPrmm2/s6KOPdpWXCHdK1uKJNCQNJodOKhYtUbI8aVKhmj/HSqQJyJX69Rs3bgzYWs18At27d09aURZFERABERABERABERABERCBoktg+PDhRuLg8MpETz75pF1++eUpgSFpcCzh45RTTrFhw4aldI6gB++///62cOHCqM379+9v5I9J1hBiyOFDyfJQu+aaa2KGfCV7Ph2XGgGJNAH5oW7WqlXLuYjJghF48MEHHa9Zs2YFO0CtREAEREAEREAEREAEREAERCCEwL///W977rnndmBCXhiqOx100EFJsyIfDXlpolmrVq1i5olJ+sQRDtxzzz1due9o9sYbbxiVpZK1q666yqgQFWr169d31Z0OP/zwZLvVcflEQCJNQLCINJQuIyGULBiBa6+91saMGWMnn3yy3XPPPcEOUisREAEREAEREAEREAEREAER+IfA0qVLjepHGzZs2IFJnz59rF+/fklzipZI1++Q8KVYwknSJw47EC8hcu9s2bIlapeU/e7atWtSp1y8eLETs9LNL6nB6KBABCTSBMJkJpEmIKiQZqjTuO2NGDHCBgwYYMR1ykRABERABERABERABERABEQgEQKR8seQk4YwHnLUJGNUU2KNF8syka8lyDgmTJiQtLMAFZ0QpEINAWr27NlWtWrVZNDpmHwmIJEmIGCJNAFBhTTD8whF+Mgjj3Qxo99++63ttddeiXekI0RABERABERABERABERABIosASo9kUaBv0MtlXLcCxYscB46sQxPHTx28tPIfYoIFcvmz59vdevWTXgYr7zyilF5KtzuuOMOFyUiy00CEmkC3heJNAFBhTQ78cQT7corr7SOHTu6Lx68aoinlImACIiACIiACIiACIiACIhAIgTIqUJulXAjxcJDDz2USFd5bStXruwqNUUzQowINcpPi1TBKvR8lSpVspUrVyY8BMKcDjvssJ2ELbxnFi1aZGXLlk24Tx2QGQISaQJylkgTEFRIs/Lly9uPP/5oJMLCYEh+mkhfron3riNEQAREQAREQAREQAREQASKCgFyqjRs2NDwgAk1wp7Gjh2bVDhQPIEEIeOnn34yhJL8MASiatWqxcxHE6sUeKwxRSvr/fzzz9sFF1yQH5ejPtNEQCJNQJASaQKC+qfZ008/bSNHjrRRo0blHUjcI9nDP/roI2vevHliHaq1CIiACIiACIiACIiACIhAkSYQLX9L7dq1XX4acq0kYhQ3ufXWW2MegpcO3jr5YfEqTHHOu+++22655ZaETh+tX4rgkN9GltsEJNIEvD8SaQKC8poRK0rM5AcffGDNmjXb4cCBAwe6LOzkp5GJgAiIgAiIgAiIgAiIgAiIQCIEKJuNCBFuXbp0caFJeNYEtXHjxlnbtm1jNqdU9cyZMxPqN8j5yd1JONKsWbNiNmfTu3379kG6dG0+++wzF8EQXi0KAYvrqF69euC+1DA7BCTSBOQukSYgKK8ZeWiwJ554IuJBF110kfuSe+aZZ4J3qpYiIAIiIAIiIAIiIAIiIAJFngBhT3jlT5s2bScWN9xwg913332BGbG5TF6aWOWv6ax///528cUXB+43SEOq315yySVxm1IGPKiHEHloYLNixYqd+n399dftrLPOins+Ncg+AYk0Ae+BRJpgoMg3wxfme++9Z+SkiWR8CRL2hNvgOeecE6xjtRIBERABERABERABERABERABjwBrjUsvvdSWLl26A4/SpUvbgw8+mLdpHATW6aefbu+8807MpqxdBg0a5HLipMMI2+rdu7chqsQyNrefffbZQKecPHmy4WX0+eef79T+pptusnvvvTdQP2qUfQISaQLeA4k08UHxRUmm8OHDhxtfkLEMNzzc9gh7OuCAA+J3rhYiIAIiIAIiIAIiIAIiIAIi8A+BaNWeEk0kPHToUOvWrVtcrueee66RuiEdRllsymPHM8K3qDAVz9gEP/XUU11O0HBLNl9PvHPq9/lHQCJNQLYSaWKDmjRpkh133HF27LHHWs+ePa1Hjx5WokSJmAcRS/rhhx+6bOwyERABERABERABERABERABEUiEAPlkyCsTbnj0k8ulZcuWcbtD4KhRo0bEEKHwg5NJ4hveB4mKSVgcz6gotXz58ri5cBh/9+7dDbEp3BIVrOKNSb/PDAGJNAE5S6QJBurNN990qvAnn3zihBoEmxYtWkQ9GPfCgw8+2GUtl4mACIiACIiACIiACIiACBQNAuvWrXNrBkQGqg7tvvvuCV844U5NmzaNKLAkItTceOONdv/99wc6/+DBg90aJxmL5v0TqS9SQ1BZKpbFEmg4jqpQWmclc6eye4xEmoD8JdIEBPVPsyVLltirr77qBJty5co5wYY/VapU2aGjX375xeWnee6556xjx46JnUStRUAEREAEREAEREAEREAEChSB33//3b7++mtbuXKlbd++3Y29YsWKSa8F5syZ45LlkgQ43IIKNfRx0EEHBebYp08fJ6CULVs20DFr1641cne+8MILgdrTaPbs2UZlqWgWT6Ah0TEJj2UFj4BEmoD3TCJNQFARmo0fP94JNvyhNB5izcknn5zXkhw2V1xxhctPs9deeyV/Ih0pAiIgAiIgAiIgAiIgAiKQswSmTJnikuVGqqZEOepDDjkkqbGT77JDhw6GGBJJqCHRMN46sezCCy9MSERp0KCB3X777W59E63sN5Wohg0bZnfeeachBAU1PHXw2IlmXCeJhyOFOHEMeWyo5pRIOfKgY1O7/CcgkSYgY4k0AUHFaIZLI541iDV42vi5awh3wsWQpMNDhgxJ/UTqQQREQAREQAREQAREQAREIGcIzJ07177//ntjPRDNdtllFzvttNOSHjNiCAmAIwlAiBWPPfaY4QETzVatWmX16tWL6JETa1DVq1d365patWpZ3bp1XdMFCxbYd99959Y29JuIUW4bL5qqVatGPAyRiyTBkUqQcwB5eCZMmCCBJhHoOdZWIk3AGyKRJiCogM2mT5+eFw6FayHeNZS144u5b9++AXtRMxEQAREQAREQAREQAREQgVwmQIGRhQsXBhrioYcemlKZ6yeffNIoNx1NDKIaLWFHvpgSPqhHHnnEyAWTTSMNBF49kYzrI68Nm9uRrFevXq7U9r777pvNS9C5UyQgkSYgQIk0AUEl0eztt992gs3o0aNt8+bNNn/+fKNUnEwEREAEREAEREAEREAERKBgEkBIILxp69atgS6A/DSNGjUyhJpULF5y3saNG9u7774bdb1BvsypU6emMoSkjyWEinOHhynhHURiY8KmInkKcULy1+AgI66PAAAgAElEQVRBE80DJ+lB6cCME5BIExC5RJqAoFJotmzZsjz3PIk0KYDUoSIgAiIgAiIgAiIgAiKQJQKENZF/5e+//w40AsSZPffc04kzhA6lw5566innMRNN0CCkiKpHJNcNF0QIIyJkKFZoVjrGGN4HSYgnTpxoTZo02eFXjAfPGpItRzOEJ0qOS6DJjzuT+T4l0gRkLpEmIKgUm+23335OAZZIkyJIHS4CIiACIiACIiACIiACWSDw2muv2bZt2wKduVixYk6UOPDAAwO1T6TRSy+9ZA8++KCRDyeanXTSSS4BL4l2Q+3555+366+/PuH8NImML7Qt4UkPPPCASwHh24wZ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7vR6OxT7/thvPnf/WThnvWPb1qnvrt9p/YpPEmnyeIOVg64l0vicZIk0PkFleZhEmiwB6nQREAEREAEREAEREAERyCOBdESaF7770vX85P1SR9v6H0+WWxs2LfrZotU/uw7vTnN///NfSWtQZTP3eNN/uwr/eMBgX//jldPh3anu13/CqEpao6o13LA9Dy727Us+fMvN/ml5qeN4vOmhbsdNqxX7mUSaYG+wn376ydWokTxsLdgeo92aRBqf8yORxieoLA+TSJMlQJ0uAiIgAiIgAiIgAiIgAnkkkI5IM/H7pa77gndLHe1Om1Z3I5r+r9T297+vcce9M6nMK5t8QGu3WYX/K59N6FS7/072dSwHcawXblXypHsa7+cO3qKORJoc3lNU8poyZYrbe++9c9hLfJqWSONzriTS+ASV5WESabIEqNNFQAREQAREQAREQAREII8E0hFplq5Z7U6a9Wqpoy0thAmRBrGmpG1fpap7Yu9/F32bfDSHvz3BkZC4pG2xSUX34v5HFvv2lR/NcDN+/L7UcYze+zBX/x9PnUSTJ02wN9jGG2/sxo4d64477rhgG45paxJpfE6cRBqfoLI8TCJNlgB1ugiIgAiIgAiIgAiIgAjkkUA6Ig3D7PVPLphx33xebMRV//GIGflPmNGWFSsX+z75aMhLU9J6N9nfkUA40R74fJ4bvuTT9Y69dsfd3Ql1GxT7/ovfLXF3fvq+JRtOtPb1tnddti9eCYqfl0eR5u9/2Lz55pvuwAMPtCpYQRrtPfXUU+6kk04KstnYtiWRxufUSaTxCSrLwyTSZAlQp4uACIiACIiACIiACIhAHgmkK9IgjAz78p8S2Mu/cj+s/d3tW6O2u+xfjV3dSsUrMHmX9Nryr92IJQvd52t+cTv/kyvmgga7uKbVa613xbRL8uAxX39mP6Ni09nb7uzO3m5n+/+S9ty3X9rxnvcNSYsRdGpsvMl6x5ZHkebee+9111xzjXvjjTfcQQcdFNgdhviDSDNhwgR31FFHBdZunBuSSONz9iTS+ASV5WESabIEqNNFQAREQAREQAREQAREII8E0hVpcj1URBcEnR2qVHObpPAAQdj5ZPVKt22lTYvy25Q2vvIo0uy1117u/fffd++9957bc889A5u2tWvXuooVK7rXX3/dNW/ePLB249yQRBqfsxclkea7774zBXPVqlVuyy23dEcffbTPq0h92Jw5c1zbtm2t/a23Ll7yLvXZ2R8hkSZ7hmpBBERABERABERABERABPJFIGoiTS44lDeRZtasWW7fffc1lF9++aXbdtttA8O6evVqt9lmm7l3333XIQTJnJNI4/MuiIpIM3DgQHf11Vc7FEdcwxo1auQ++uijorjA4cOHu3322cc1adKk2JVNmjTJtWvXzvH1gAMOKPWqV6xYYed+/vnn9qlfv75POsEdJpEmOJZqSQREQAREQAREQAREQATCJiCR5rywkee8P8KQXn75Zevn559/dlWrVg2szx9++MFtscUW7pNPPnE77bRTYO3GuSGJND5nLwoiDQLMWWed5e6++253xRVXuP/85z+uW7du7umnn7YkS3/88YerUqWKiTDTp08vdmWMH0OkqVChQqlXTTbt559/3n4mkcbnjaHDREAEREAEREAEREAEREAEighIpCkskWbkyJHuzDPPdJ06dXLMLXvOkvvJL774wi1YsMC1atVqvZXw22+/2TllCTt45uAc8M0337g6depYO4sXL3atW7cuamvdunUObx7Com688Ubb726//fauQYMG9qlbt67boJQ8Qzg1IC59++23rkWLFm6HHXaIxUqVSONzmvIt0vzyyy8OL5PzzjvP3XXXXTbqX3/91dWsWdNdcsklrnfv3vY9xJrnnnvO/fjjj27TTTe17xE32LRpU/fBBx+43XffvcwrJg6QBYEQJJHG542hw0RABERABERABERABERABCTS/EMAEYH9WqEYe0L2keecc47tIxFqEEwQRA499FB3ww03WAQHoVAIIUR9XHzxxXb5P/30kzv33HPduHHjLAIEb5m33nrL7bzzzsXwzJ071+26666WyoOwp2OOOcaEmoULFxYdR+Wn9u3bu9tvv93amDdvnlu6dKn766+/7JhNNtnE3Xfffe6iiy4qOmfRokXulFNOsTAqDGHpiSeecCeffHLkp0cijc8pyrdI069fP1MNv/76a1e9evWiUffq1csSNx155JH2PZRC3NEmTpxY9L0zzjjDff/99+ZFk8pefPFF16ZNG7vp69Wrl+rwwH+ucKfAkapBERABERABERABERABEQiNgDxpCkOk+f33362K00YbbWT5SkePHm0eNYg0GKFJ7BsnT57sCFlCBGH/iIiyfPly17JlS4ejQY8ePUzEufPOO93YsWNNgMHzxbOZM2eaZwyCC+LPLrvsYkKN54TAcccee6x75ZVX7Fw8Z7A///zT3XLLLRZlgvXp08d16dLF/h/BaL/99jPRbNCgQdYmUSOVKlVyb7/9dmhrIdOOJNL4JJdvkQblkIXy7LPPJh0xx6BS4l2DgIOC2LBhQzd+/Hi72VPZqFGj3GmnnWbKZ6IYlOq8oH4ukSYokmpHBERABERABERABERABMInIJGmMEQaPIIQVYjKqFatmoksCCGIJxdccIHbZpttbM+I9wsRGeSUwdMGjxg8b1577TU3e/ZsK3SDsMOeFO+Y888/3z344INFN+arr75qhWs4D0PAQWy59tpr7d/sZxs3buwuvfTSIuGGxM2XX365e+aZZ9zBBx/s7rjjjmKVoehjyJAhtv89/PDDTdx56KGHbHyPPPJI+IsizR4l0vgElm+RBtetGTNmmLtWafF2iZdx/PHH2wLAdezss88uWjTUn09lDz/8sLnokZh44403TnV44D+XSBM4UjUoAiIgAiIgAiIgAiIgAqERKE2k8cJj2GwXgi1ZssRSTJRmvCyPuyFoIMSwH8QbBY8YzxA6cAYgfwz7SyIxiOTAe6Z27dqWYoP9KHvKm266yU7r3r27u+222yyPDPlrKOVNiBP20ksvmZcL+0/aYz+I8EI+VhwQqGSM2EPoFU4EhFQRYYI4NGDAAHfiiScWw42AQ44bvGzmz59vTg60Sx8PPPCAq1GjRuSnRyKNzynKt0jz8ccfWxzghRdeaPF2ZSX/5XI8b5h77rnH4gRRPFEa/Vj//v0tKbEXa+jnnCCPkUgTJE21JQIiIAIiIAIiIAIiIALhEigp0rDRr1y5clEoC9Vk42wkuJ0yZYqJCmvWrCkK//GuKe4iDflfOnToYLlodtxxR9esWTOrAEzaDQQRCtRQgtvLl4rHimd8n/0nxWjmzJlj4VGEPxEuhaBCdEjz5s0tfyr9IMbgSYO3y2effWahTDgn4GmDd82nn35qlYzJbTN06FATxhCAML4SxlSrVi0bJx/y3dAewo6Xj9ULo/LjsBCV+1Iijc+ZyLdIwzC5Kckvg2sZHjLckKi4CCrXXHONqYkYGbRxE8P9jNAnMmZT9cmPecKOl4TJzzlBHiORJkiaaksEREAEREAEREAEREAEwiXgiTSIMyR0LflymbwgbKaTFTQJd8T+evMiFShB7UU2kBAXsYYPe7K4Jw6mavCpp55qjgH3339/sQgOL/eplxyYSsOIMFtttVURQEKiqOTEnhWhB6GGue7Zs6flr8HYmyLKkDeGECnEEzxf8M7BsQC+FLLx0nx4FZqoHEXeVH6G8INAVtLYJ3MeQhDHE66VzLnB38yHf5REGp/MoyDSMNRly5Y5kgiTGJibmupO3OR4wCQKMSiQgwcPtng+PGn8GhmzOR6BJx8mkSYf1NWnCIiACIiACIiACIiACARD4NFHHzXBApEmWZqGzTff3Aqg5KNYSTpXitcH5Z+/++67Mk/jBTdCDdcb5+pO7CH32GMPE2lKGoVoSMZ76623msNANgYvRBbPyYCUHoRK4YnjGSFWJP299957zYsnmdgCewSclStX2j1F3hmugeTG5FvlXqONN99803344YeWy4bqVFE1iTQ+ZyYqIo3P4VrcHgLNY489VpQB28+5uO/hGkZcYT5MIk0+qKtPERABERABERABERABEQiGAFWA8Kbwa40aNXJ7772338NDPY4QHKoBeRWNUnWO10fHjh1THaaf+yTQokULC7Xq27evzzP+dxjCGp49hGetXr3aBCDCq0gm3K5du7TbC/MEiTQ+acdNpPF5WZE7TCJN5KZEAxIBERABERABERABERABXwQQaEjQSlllPBf8ihvkrMHrISohUPPmzXN82NynKtrigYmLZ5CviYzAQVR14p7A+WCvvfaKwIjCG4JEGp+sJdL4BJXlYRJpsgSo00VABERABERABERABEQgZAJ//vmnO+mkk6z6DlV5sF9//dWSuBKC4tcIVfGq/vg9J+jjqD6E94VfcYbjGHeTJk2CHkq5bu/mm2+2nKzktSlvJpHG54xLpPEJKsvDJNJkCVCni4AIiIAIiIAIiIAIiECIBFasWGECzW677WZ5MksahU5I9OonBOrAAw+0Ms35NHKbTJ061dcQKEN9yCGHWP4dWXAE8MAimXDnzp1dt27dgms4Ji1JpPE5URJpfILK8jCJNFkC1OkiIAIiIAIiIAIiIAIiEBKBxYsXm0BDno8ePXqU2SsCzTvvvGOVfcqqIku4EO1EwcaPH+9WrVpV5lAYKwl2qSZUyIYXVLVq1Xx7FQXF4rXXXnMtW7Z0L730kmvdunVQzcamHYk0PqdKIo1PUFkeJpEmS4A6XQREQAREQAREQAREQARCIPDee++ZQENFoK5du/rqkQpBVPJZvnx5seMJGSJJ7HbbbeernVwfRBVdRKWSRu4cSkrjNVQe7Nhjj7UqSFdffXWolzt//nzLT0Ti5qhX/8oFmLyINH7dx3JxwZm2SaWkTp06WbxhvizKZcKCYiKRJiiSakcEREAEREAEREAEREAEckOA/RwCzV133WUhKenawoULraIsZZixWrVq5a26bFljnzhxYpGYRPlnqgM1a9bMbbTRRulebmyPP+KII0yQ6tOnT1bXcN9991n566efftp3O+Q5SlZ223dDMTwwLyINSmncBAd+kRBz6NVyD3uup02b5gYPHuzOP//8sLsOtT+JNKHiVmciIAIiIAIiIAIiIAIikBYBQoEQaEaOHOlOOeWUtM5NPJiwJ7xqli1bZnvDKlWqZNxWLk5cu3ate/HFF00oYHxVq1bNRTeRbvOwww5zjRs3dvfff39W40Ts+fDDD923336bVTvl5eS8iTTUkJf5J7DDDjs4knKhOJNEqVBNIk2hzqyuSwREQAREQAREQAREIO4EHnvsMXfxxRe7sWPHuiOPPDLulxOr8SMabbLJJqGOmaTIBxxwgLv77ruz6pcwNgQarkGWmoBEmtSMInFEq1atLB7vu+++swRKhWoSaQp1ZnVdIiACIiACIiACIiACcSYwYMAA16tXLzdmzBgL+5EFT4AQnzZt2rgzzzzTnX766UUdwPzss882seSSSy4JvuMyWqTa1tFHH+0oh52pEdK26aabWvJhEkhvuOGGmTYVq/MoH77ffvtZNE66JpEmXWJ5Or5t27bujDPOcCNGjHD77LNPwZYik0iTpxtM3YqACIiACIiACIiACIhAGQR69uzpnnzySRNoGjVqJE45IkDOFkLIRo8eXRRKRshVu3btXI0aNdxDDz3k2BeGZfvuu6+JReRnzdQ+/vhj16RJE8s7RGhbebHLLrvMbbHFFq579+5pX7JEmrSR5Tn4PE4AACAASURBVOeELbfc0pFBnTCxvffe2+FqWIguhhJp8nN/qVcREAEREAEREAEREAERKI3ANddcY0lfEWi23nprQcohARL0Uklp5syZ5oVBKWrKkjdo0MBNmjQp9JLfVFhCbMgkObSHiSpZ+++/v8Mrh/uovBjr5quvvnJPPPFE2pcskSZtZOGfMHfuXFNPKQWHEQN67bXXWqKt6tWrhz+gHPYokSaHcNW0CIiACIiACIiACIiACKRBgKIlS5YsMYGGkBVZbgnceOONFlK2atUqy0VKwt2aNWuauEF1qZL2yy+/mJDTsmXLYvNDUmbOX7RokZUM32OPPYpOJeSIojQkBS4r9Oj22293L7zwglW3uvPOO92pp55arOvVq1e7559/3hwIWrdubV4+ntG3V76cfDbTp0935LahEtj1119frJ3Zs2fbuHfZZZdSwSbrx89MkAMHwWunnXZaL+xo1KhRDg+xpUuXur322stCzAgpSzTmYeONN3aVKlUqszuOwdsJD7NEzt26dbM0JbNmzfIz1GLHSKRJG1n4JzDBX375pXv00UeLOs9GmQv/Cvz3KJHGPysdKQIiIAIiIAIiIAIiIAK5ItC+fXvLI8JmVhYOgXPPPdcEGbxmSHHx+++/u7fffrtYiBn7wGrVqpngQWTFlClT3Mknn+yeeuopGyShUldddZX7+uuviwbdtGlT+/6OO+5onjp47Dz77LPu+OOPX+/CaIe5p6IToTrDhg2zPDmeITycd9557ptvvrFvEcb01ltvmRDy/fffWw4bxBeMJNOcy/feeOMNd9BBB9n3uUYEkU8//dT+TaWwxBw8fC9ZP35mY+jQoQ5WP/30k1VoxsGBMWJEqOCptM0225iA9MMPP7hXXnnFHXfcce7xxx93CxYsMNHm/ffft+MRm/h5yUrPXOexxx5bxKJFixZWZnyrrbYyUerhhx92VInGuP558+a5jh07JhV9OFYijZ8ZzuMx3CC77rqrTW7Jqk4HH3ywlb+7/PLL8zjCYLuWSBMsT7UmAiIgAiIgAiIgAiIgAukQwDuDPQZ7jwcffDCdU3VslgTwbpkzZ455zfCVcueJAgnNE3qEh0zdunVNmMF7AzHh559/dq+//ro76qijzEMGLxGSDCMM9O7d27388svm8YEgMnjwYBMd8NTBW8Xzkvroo49MSDnxxBPdI4884ipXruwmTpxoQgaGIET7eO7g8YOnSvPmzd2VV15pogSCBWMgdw77Otru16+fCUJ43nAtCFB431DaG2cE0npw7D333GOiYP/+/VP2kwrzrbfe6vAGIhoFoQjvHkKuGN+FF17opk6dahyIWKFv7LPPPrNxImiR92fPPfd0N910k3kiHX744eYthFcOY/SMnD14yiBU4XEEE9hTbhwGJHom5GnQ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1vBVJgIiIAIiIAIiIAIiIAJRJUAF07ffftsEmjp16kR1mBqXCIhAGgQk0qQBK06HSqTJfLb69u1rYg3hT4g1u+++e+aN6UwREAEREAEREAEREAERyAGBzp07u6+++soEmsqVK+egBzUpAiKQDwISafJBPYQ+JdJkB5kKUAg1fKgChVgj99HsmOpsERABERABERABERCBYAhQualChQruySefDKZBtSICIhAZAhJpIjMVwQ5EIk0wPH/99dcisaZTp04m1jRo0CCYxtWKCIiACIiACIiACIiACKRBYNWqVVZie/vtt3eDBw9O40wdKgIiEBcCEmniMlNpjlMiTZrAUhz+448/Fok1lDZErKEqlEwEREAEREAEREAEREAEwiCwdOlSE2gOPfRQ16tXrzC6VB8iIAJ5ICCRJg/Qw+hSIk1uKH/77bcm1vTr18+EGj41a9bMTWdqVQREQAREQAREQAREQAT+ITB37lwTaM4880x34403iokIiEABE5BIU6CTK5EmtxP7xRdfmFgzbNiwIrFm0003zW2nal0EREAEREAEREAERKDcEXjrrbdMoLnlllvcxRdfXO6uXxcsAuWNgESaAp1xiTThTOwnn3xiYg1Z9T3PGpK4yURABERABERABERABEQgWwITJkwwgWbIkCHujDPOyLY5nS8CIhADAhJpYjBJmQxRIk0m1DI/58MPPzSxZtKkSSbWXHXVVZk3pjNFQAREQAREQAREQATKPYFRo0Y5ClfwMrBNmzblnocAiEB5ISCRpkBnWiJNfiZ21qxZJtbMnDnTxJpLLrkkPwNRryIgAiIgAiIgAiIgArEl8OCDD1p4EwJNixYtYnsdGrgIiED6BCTSpM8sFmdIpMnvNL355psm1sybN8917drVde7cOb8DUu8iIAIiIAIiIAIiIAKxIEDlpqFDh5pAs/vuu8dizBqkCIhAcAQk0gTHMlItSaSJxnS89tprJtZQMhHPGjLyy0RABERABERABERABESgNAJUbnrllVdMoKlfv74giYAIlEMCEmkKdNIl0kRrYidOnGhizcqVK02sad++fbQGqNGIgAiIgAiIgAiIgAjklQBh8vPnzzeBpkaNGnkdizoXARHIHwGJNPljn9OeJdLkFG/GjY8fP97Emr///tvEmrZt22bclk4UAREQAREQAREQAREoDAIdOnRwa9ascWPGjHEbbLBBYVyUrkIERCAjAhJpMsIW/ZMk0kR7jp566ikTa6pWrWpiTevWraM9YI1OBERABERABERABEQgcAJr1661Etu1a9d2jzzySODtq0EREIH4EZBIE7858zViiTS+MOX9oBEjRphYU69ePRNrDjvssLyPSQMQAREQAREQAREQARHIPYFly5aZQLP33nu7vn375r5D9SACIhALAhJpYjFN6Q9SIk36zPJ5Bhn8EWsaNWpkYs1BBx2Uz+GobxEQAREQAREQAREQgRwSWLhwoQk07dq1c927d89hT2paBEQgbgQk0sRtxnyOVyKNT1ARO2zgwIEm1uy///4m1uyzzz4RG6GGIwIiIAIiIAIiIAIikA2B2bNnm0BzxRVXuC5dumTTlM4VAREoQAISaQpwUrkkiTTxntg+ffqYWHPEEUeYWLPbbrvF+4I0ehEQAREQAREQAREQAffaa6+ZQNO7d293zjnniIgIiIAIrEdAIk2B3hQSaeI/sX/++acJNXz4Y45Ys/POO8f/wnQFIiACIiACIiACIlAOCTzzzDPupJNOck8//bQ928lEQAREoDQCEmkK9L6QSFM4E7t69eoisebss882saZ+/fqFc4G6EhEQAREQAREQAREocALDhg1zV155pRs7dqw7/PDDC/xqdXkiIALZEJBIkw29CJ/riTTvvfdehEe5/tD22GMPN23aNNegQYNYjTuMwf7www9FYg0xzIg1derUCaNr9SECIiACIiACIiACIpAhgfvuu8+qN40ZM8btu+++Gbai00RABMoLAYk0BTrTiDQtWrRwv/zyS+yuEGFJIk3Z0/bNN9+YWDNgwAATavhsvvnmsZtnDVgEREAEREAEREAECp3AbbfdZt4zCDQKWy/02db1iUAwBCTSBMMxcq3ENdwpciAjPCDmGLFm+PDhRWJNlSpVIjxiDU0EREAEREAEREAEyg+Bq666yv33v/81kWbLLbcsPxeuKxUBEciKgESarPBF92SJNNGdm6BHtmDBAhNrxo0bVyTWbLTRRkF3o/ZEQAREQAREQAREQAR8Ejj33HPdd999ZwJNxYoVfZ6lw0RABETAOYk0BXoXSKQp0IlNcllz5swxsebVV181saZLly7lD4KuWAREQAREQAREQATyTIAKTggzI0eOzPNI1L0IiEAcCUikieOs+RizRBofkAr0ENxqEWv42rVrV3fJJZcU6JXqskRABERABERABEQgOgR+/vlnK6290047uUGDBkVnYBqJCIhArAhIpInVdPkfrEQa/6wK9cg33njDxJr58+ebZ815551XqJeq6xIBERABERABERCBvBL48ssvHR40LVu2dHfddVdex6LORUAE4k1AIk2856/M0UukKdCJzeCyCH+699573VdffWViTceOHTNoRaeIgAiIgAiIgAiUFwJr1qxxy5Ytc8uXL7dPyf/n52vXrnW///67fbz/976uW7fOwn022WSTYl8Tv1ezZk1Xq1YtV7t27WJf+V6NGjVihfrDDz80gebss892119/fazGrsGKgAhEj4BEmujNSSAjkkgTCMaCamTChAnmWbNq1SoTa0455ZSCuj5djAiIgAiIgAiIgH8CvLxZuHBhsc+nn37qFi1a5P766y8TTsoSUagmmUyA2XDDDYuJN6UJOStWrFhPBPLEoF9//dXVr1/f7bjjjqV+olQgAc9lQpy6d+/uLrroIv8ToCNFQAREoAwC5U6kIfTj3XfftT8KrVu3tpjRoG3mzJn2B69u3bquWbNmLh9lkSXSBD2rhdPes88+a2LNBhtsYGLN8ccfXzgXpysRAREQAREQARFYjwDCy4wZMxzPqHydN2+eq1q1apkiSLVq1fJKEY+czz77bD0RyROVEG/2339/e87m67777puX8b744osm0DzyyCPu9NNPz8sY1KkIiEDhESg3Ig2/7FG3+SXK5vTvv/92F1xwgRs8eLDNKqIGngalKeDEllauXNm98MILSe+AJUuWmKvjO++8U3Tc1ltv7SZOnOh22223UO8eiTSh4o5lZ6NHjzaxpnr16ibWHHXUUbG8Dg1aBERABERABESgOAGEmOnTpxeJMnieeIIGX3kuzbcQk+mc4eXzySefmNjkCU/8O1G0adu2babN+z7viSeecJTZpsT20Ucf7fs8HSgCIiACqQiUG5GGGFF+ibIxPfjggy3UY9KkSSbOIKTgonjbbbdZ+eLDDjusiNu0adPs3wg4rVq1Ssrz8MMPd1OnTnX80j7yyCPNY+e0005zBx54oHvmmWdSzUWgP5dIEyjOgm7sscceM7Fm2223NbHm3//+d0Ffry5OBERABERABAqRwOzZs+15kw/hRjy3esJFgwYNCvGSi65p5cqVRYLU66+/bi9MTzjhBPsce+yx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)" - ] + "# from evals.eval_on_hotpot import eval_on_hotpotQA\n", + "# from evals.eval_on_hotpot import answer_with_cognee\n", + "# from evals.eval_on_hotpot import answer_without_cognee\n", + "# from evals.eval_on_hotpot import eval_answers\n", + "# from cognee.base_config import get_base_config\n", + "# from pathlib import Path\n", + "# from tqdm import tqdm\n", + "# import wget\n", + "# import json\n", + "# import statistics" + ], + "id": "5f36b67668fdb646", + "outputs": [], + "execution_count": 2 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-24T15:57:30.764970Z", + "start_time": "2024-12-24T15:57:07.861187Z" + } + }, + "cell_type": "code", + "source": [ + "# answer_provider = answer_without_cognee # For native LLM answers use answer_without_cognee\n", + "# num_samples = 10 # With cognee, it takes ~1m10s per sample\n", + "# \n", + "# base_config = get_base_config()\n", + "# data_root_dir = base_config.data_root_directory\n", + "# \n", + "# if not Path(data_root_dir).exists():\n", + "# Path(data_root_dir).mkdir()\n", + "# \n", + "# filepath = data_root_dir / Path(\"hotpot_dev_fullwiki_v1.json\")\n", + "# if not filepath.exists():\n", + "# url = 'http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json'\n", + "# wget.download(url, out=data_root_dir)\n", + "# \n", + "# with open(filepath, \"r\") as file:\n", + "# dataset = json.load(file)\n", + "# instances = dataset if not num_samples else dataset[:num_samples]\n", + "# answers = []\n", + "# for instance in tqdm(instances, desc=\"Getting answers\"):\n", + "# answer = await answer_provider(instance)\n", + "# answers.append(answer)" + ], + "id": "d5af4b516c6621a3", + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Getting answers: 100%|██████████| 10/10 [00:13<00:00, 1.31s/it]\n" + ] + } + ], + "execution_count": 9 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-24T15:57:30.787382Z", + "start_time": "2024-12-24T15:57:30.785259Z" + } + }, + "cell_type": "code", + "source": [ + "# from evals.deepeval_metrics import f1_score_metric\n", + "# from evals.deepeval_metrics import em_score_metric" + ], + "id": "2bf69048a272158c", + "outputs": [], + "execution_count": 10 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-24T15:57:30.828509Z", + "start_time": "2024-12-24T15:57:30.795197Z" + } + }, + "cell_type": "code", + "source": [ + "# f1_metric = f1_score_metric()\n", + "# eval_results = await eval_answers(instances, answers, f1_metric)\n", + "# avg_f1_score = statistics.mean([result.metrics_data[0].score for result in eval_results.test_results])\n", + "# print(\"F1 score: \", avg_f1_score)" + ], + "id": "72ba5f89cccbee6b", + "outputs": [ + { + "data": { + "text/plain": [ + "✨ You're running DeepEval's latest \u001B[38;2;106;0;255mOfficial hotpot F1 score Metric\u001B[0m! \u001B[1;38;2;55;65;81m(\u001B[0m\u001B[38;2;55;65;81musing \u001B[0m\u001B[3;38;2;55;65;81mNone\u001B[0m\u001B[38;2;55;65;81m, \u001B[0m\u001B[38;2;55;65;81mstrict\u001B[0m\u001B[38;2;55;65;81m=\u001B[0m\u001B[3;38;2;55;65;81mFalse\u001B[0m\u001B[38;2;55;65;81m, \u001B[0m\u001B[38;2;55;65;81masync_mode\u001B[0m\u001B[38;2;55;65;81m=\u001B[0m\u001B[3;38;2;55;65;81mTrue\u001B[0m\u001B[1;38;2;55;65;81m)\u001B[0m\u001B[38;2;55;65;81m...\u001B[0m\n" + ], + "text/html": [ + "
✨ You're running DeepEval's latest Official hotpot F1 score Metric! (using None, strict=False, async_mode=True)...\n",
+       "
\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Event loop is already running. Applying nest_asyncio patch to allow async execution...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating 10 test case(s) in parallel: |██████████|100% (10/10) [Time Taken: 00:00, 407.84test case/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.0, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: Were Scott Derrickson and Ed Wood of the same nationality?\n", + " - actual output: Scott Derrickson is described as an American filmmaker in the context. Ed Wood is referenced as a cult filmmaker in the American biographical film \"Ed Wood.\" Therefore, both Scott Derrickson and Ed Wood are of the same nationality: American.\n", + " - expected output: yes\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.04255319148936171, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: What government position was held by the woman who portrayed Corliss Archer in the film Kiss and Tell?\n", + " - actual output: The woman who portrayed Corliss Archer in the film \"Kiss and Tell\" was Shirley Temple. After her acting career, she held the position of U.S. Ambassador to Czechoslovakia from 1989 to 1992, and she was also a member of the U.S. delegation to the United Nations from 1969 to 1970.\n", + " - expected output: Chief of Protocol\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.05882352941176471, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: What science fantasy young adult series, told in first person, has a set of companion books narrating the stories of enslaved worlds and alien species?\n", + " - actual output: The book series that fits the description is \"Animorphs\". It is a science fantasy young adult series told in the first person with multiple narrators, and it deals with themes relevant to enslaved worlds and alien species.\n", + " - expected output: Animorphs\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.0, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: Are the Laleli Mosque and Esma Sultan Mansion located in the same neighborhood?\n", + " - actual output: The Laleli Mosque is located in Laleli, Fatih, Istanbul. The Esma Sultan Mansion is located in the Ortaköy neighborhood of Istanbul. Therefore, they are not located in the same neighborhood.\n", + " - expected output: no\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.22222222222222218, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: The director of the romantic comedy \"Big Stone Gap\" is based in what New York city?\n", + " - actual output: The director of the romantic comedy \"Big Stone Gap\" is Adriana Trigiani, and there is no specific New York city mentioned in the provided context.\n", + " - expected output: Greenwich Village, New York City\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.11764705882352941, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: 2014 S/S is the debut album of a South Korean boy group that was formed by who?\n", + " - actual output: The South Korean boy group that debuted with the album \"2014 S/S\" was formed by FNC Entertainment.\n", + " - expected output: YG Entertainment\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.18181818181818182, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: Who was known by his stage name Aladin and helped organizations improve their performance as a consultant?\n", + " - actual output: Eenasul Fateh, known by his stage name Aladin, is a Bangladeshi-British cultural practitioner, magician, live artist, and former international management consultant.\n", + " - expected output: Eenasul Fateh\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.0, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: The arena where the Lewiston Maineiacs played their home games can seat how many people?\n", + " - actual output: The Lewiston Maineiacs played their home games at the Androscoggin Bank Colisée. The capacity of the arena is not directly stated in the context, but it is implied from the information provided.\n", + " - expected output: 3,677 seated\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.12903225806451613, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: Who is older, Annie Morton or Terry Richardson?\n", + " - actual output: Annie Morton was born on October 8, 1970, while Terry Richardson's birthdate is not provided in the context. Therefore, based on the available information, Annie Morton is older than Terry Richardson.\n", + " - expected output: Terry Richardson\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Metrics Summary\n", + "\n", + " - ❌ Official hotpot F1 score (score: 0.0, threshold: 0.5, strict: False, evaluation model: None, reason: None, error: None)\n", + "\n", + "For test case:\n", + "\n", + " - input: Are Local H and For Against both from the United States?\n", + " - actual output: Yes, Local H is from the United States, specifically mentioned as a band from the Chicago suburbs, and For Against is also a band mentioned as being from the United States, although specific details about For Against's origin are not provided in the context.\n", + " - expected output: yes\n", + " - context: None\n", + " - retrieval context: None\n", + "\n", + "======================================================================\n", + "\n", + "Overall Metric Pass Rates\n", + "\n", + "Official hotpot F1 score: 0.00% pass rate\n", + "\n", + "======================================================================\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/plain": [ + "\u001B[38;2;5;245;141m✓\u001B[0m Tests finished 🎉! Run \u001B[32m'deepeval login'\u001B[0m to save and analyze evaluation results on Confident AI. \n", + "‼️ Friendly reminder 😇: You can also run evaluations with ALL of deepeval's metrics directly on Confident AI \n", + "instead.\n" + ], + "text/html": [ + "
 Tests finished 🎉! Run 'deepeval login' to save and analyze evaluation results on Confident AI. \n",
+       "‼️  Friendly reminder 😇: You can also run evaluations with ALL of deepeval's metrics directly on Confident AI \n",
+       "instead.\n",
+       "
\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F1 score: 0.0752096441829576\n" + ] + } + ], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-05T19:23:30.332977Z", + "start_time": "2025-01-05T19:23:30.331538Z" + } + }, + "cell_type": "code", + "source": "", + "id": "783985c35d1126de", + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -865,6 +1439,22 @@ "# Give us a star if you like it!\n", "https://github.com/topoteretes/cognee" ] + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": "", + "id": "d042efe5d38144fa" + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": "", + "id": "9436af97520e0ae" } ], "metadata": { diff --git a/notebooks/cognee_hotpot_eval.ipynb b/notebooks/cognee_hotpot_eval.ipynb index 32b34a36b..445a13c1d 100644 --- a/notebooks/cognee_hotpot_eval.ipynb +++ b/notebooks/cognee_hotpot_eval.ipynb @@ -38,8 +38,8 @@ "metadata": {}, "outputs": [], "source": [ - "answer_provider = answer_with_cognee # For native LLM answers use answer_without_cognee\n", - "num_samples = 10 # With cognee, it takes ~1m10s per sample\n", + "answer_provider = answer_with_cognee # For native LLM answers use answer_without_cognee\n", + "num_samples = 10 # With cognee, it takes ~1m10s per sample\n", "\n", "base_config = get_base_config()\n", "data_root_dir = base_config.data_root_directory\n", @@ -49,7 +49,7 @@ "\n", "filepath = data_root_dir / Path(\"hotpot_dev_fullwiki_v1.json\")\n", "if not filepath.exists():\n", - " url = 'http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json'\n", + " url = \"http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json\"\n", " wget.download(url, out=data_root_dir)\n", "\n", "with open(filepath, \"r\") as file:\n", @@ -87,7 +87,9 @@ "source": [ "f1_metric = f1_score_metric()\n", "eval_results = await eval_answers(instances, answers, f1_metric)\n", - "avg_f1_score = statistics.mean([result.metrics_data[0].score for result in eval_results.test_results])\n", + "avg_f1_score = statistics.mean(\n", + " [result.metrics_data[0].score for result in eval_results.test_results]\n", + ")\n", "print(\"F1 score: \", avg_f1_score)" ] }, @@ -99,7 +101,9 @@ "source": [ "em_metric = em_score_metric()\n", "eval_results = await eval_answers(instances, answers, em_metric)\n", - "avg_em_score = statistics.mean([result.metrics_data[0].score for result in eval_results.test_results])\n", + "avg_em_score = statistics.mean(\n", + " [result.metrics_data[0].score for result in eval_results.test_results]\n", + ")\n", "print(\"EM score: \", avg_em_score)" ] }, @@ -126,8 +130,12 @@ "metadata": {}, "outputs": [], "source": [ - "eval_results = await eval_answers(instances, answers, correctness_metric) # note that instantiation is not needed for correctness metric as it is already an instance\n", - "avg_correctness_score = statistics.mean([result.metrics_data[0].score for result in eval_results.test_results])\n", + "eval_results = await eval_answers(\n", + " instances, answers, correctness_metric\n", + ") # note that instantiation is not needed for correctness metric as it is already an instance\n", + "avg_correctness_score = statistics.mean(\n", + " [result.metrics_data[0].score for result in eval_results.test_results]\n", + ")\n", "print(\"Correctness score: \", avg_correctness_score)" ] }, @@ -154,8 +162,12 @@ "outputs": [], "source": [ "relevancy_metric = AnswerRelevancyMetric()\n", - "eval_results = await eval_answers(instances, answers, relevancy_metric) # note that instantiation is not needed for correctness metric as it is already an instance\n", - "avg_relevancy_score = statistics.mean([result.metrics_data[0].score for result in eval_results.test_results])\n", + "eval_results = await eval_answers(\n", + " instances, answers, relevancy_metric\n", + ") # note that instantiation is not needed for correctness metric as it is already an instance\n", + "avg_relevancy_score = statistics.mean(\n", + " [result.metrics_data[0].score for result in eval_results.test_results]\n", + ")\n", "print(\"Relevancy score: \", avg_relevancy_score)" ] }, @@ -174,7 +186,9 @@ "source": [ "answer_provider = answer_without_cognee\n", "f1_metric = f1_score_metric()\n", - "f1_score = await eval_on_hotpotQA(answer_provider, num_samples=10, eval_metric=f1_metric) # takes ~1m10s per sample\n", + "f1_score = await eval_on_hotpotQA(\n", + " answer_provider, num_samples=10, eval_metric=f1_metric\n", + ") # takes ~1m10s per sample\n", "print(\"F1 score: \", f1_score)" ] }, diff --git a/notebooks/cognee_llama_index.ipynb b/notebooks/cognee_llama_index.ipynb index ec899aaea..c7c28af5f 100644 --- a/notebooks/cognee_llama_index.ipynb +++ b/notebooks/cognee_llama_index.ipynb @@ -56,10 +56,7 @@ "metadata": {}, "outputs": [], "source": [ - "documents = [\n", - " Document(text=f\"{row['title']}: {row['text']}\")\n", - " for i, row in news.iterrows()\n", - "]" + "documents = [Document(text=f\"{row['title']}: {row['text']}\") for i, row in news.iterrows()]" ] }, { @@ -78,33 +75,33 @@ "import os\n", "\n", "# Setting environment variables\n", - "if \"GRAPHISTRY_USERNAME\" not in os.environ: \n", + "if \"GRAPHISTRY_USERNAME\" not in os.environ:\n", " os.environ[\"GRAPHISTRY_USERNAME\"] = \"\"\n", "\n", - "if \"GRAPHISTRY_PASSWORD\" not in os.environ: \n", + "if \"GRAPHISTRY_PASSWORD\" not in os.environ:\n", " os.environ[\"GRAPHISTRY_PASSWORD\"] = \"\"\n", "\n", "if \"LLM_API_KEY\" not in os.environ:\n", " os.environ[\"LLM_API_KEY\"] = \"\"\n", "\n", "# \"neo4j\" or \"networkx\"\n", - "os.environ[\"GRAPH_DATABASE_PROVIDER\"]=\"networkx\" \n", + "os.environ[\"GRAPH_DATABASE_PROVIDER\"] = \"networkx\"\n", "# Not needed if using networkx\n", - "#os.environ[\"GRAPH_DATABASE_URL\"]=\"\"\n", - "#os.environ[\"GRAPH_DATABASE_USERNAME\"]=\"\"\n", - "#os.environ[\"GRAPH_DATABASE_PASSWORD\"]=\"\"\n", + "# os.environ[\"GRAPH_DATABASE_URL\"]=\"\"\n", + "# os.environ[\"GRAPH_DATABASE_USERNAME\"]=\"\"\n", + "# os.environ[\"GRAPH_DATABASE_PASSWORD\"]=\"\"\n", "\n", "# \"pgvector\", \"qdrant\", \"weaviate\" or \"lancedb\"\n", - "os.environ[\"VECTOR_DB_PROVIDER\"]=\"lancedb\" \n", + "os.environ[\"VECTOR_DB_PROVIDER\"] = \"lancedb\"\n", "# Not needed if using \"lancedb\" or \"pgvector\"\n", "# os.environ[\"VECTOR_DB_URL\"]=\"\"\n", "# os.environ[\"VECTOR_DB_KEY\"]=\"\"\n", "\n", "# Relational Database provider \"sqlite\" or \"postgres\"\n", - "os.environ[\"DB_PROVIDER\"]=\"sqlite\"\n", + "os.environ[\"DB_PROVIDER\"] = \"sqlite\"\n", "\n", "# Database name\n", - "os.environ[\"DB_NAME\"]=\"cognee_db\"\n", + "os.environ[\"DB_NAME\"] = \"cognee_db\"\n", "\n", "# Postgres specific parameters (Only if Postgres or PGVector is used)\n", "# os.environ[\"DB_HOST\"]=\"127.0.0.1\"\n", @@ -128,8 +125,12 @@ "source": [ "from typing import Union, BinaryIO\n", "\n", - "from cognee.infrastructure.databases.vector.pgvector import create_db_and_tables as create_pgvector_db_and_tables\n", - "from cognee.infrastructure.databases.relational import create_db_and_tables as create_relational_db_and_tables\n", + "from cognee.infrastructure.databases.vector.pgvector import (\n", + " create_db_and_tables as create_pgvector_db_and_tables,\n", + ")\n", + "from cognee.infrastructure.databases.relational import (\n", + " create_db_and_tables as create_relational_db_and_tables,\n", + ")\n", "from cognee.modules.users.models import User\n", "from cognee.modules.users.methods import get_default_user\n", "from cognee.tasks.ingestion.ingest_data_with_metadata import ingest_data_with_metadata\n", @@ -139,8 +140,13 @@ "await cognee.prune.prune_data()\n", "await cognee.prune.prune_system(metadata=True)\n", "\n", + "\n", "# Add the LlamaIndex documents, and make it available for cognify\n", - "async def add(data: Union[BinaryIO, list[BinaryIO], str, list[str]], dataset_name: str = \"main_dataset\", user: User = None):\n", + "async def add(\n", + " data: Union[BinaryIO, list[BinaryIO], str, list[str]],\n", + " dataset_name: str = \"main_dataset\",\n", + " user: User = None,\n", + "):\n", " await create_relational_db_and_tables()\n", " await create_pgvector_db_and_tables()\n", "\n", @@ -149,6 +155,7 @@ "\n", " await ingest_data_with_metadata(data, dataset_name, user)\n", "\n", + "\n", "await add(documents)\n", "\n", "# Use LLMs and cognee to create knowledge graph\n", @@ -198,7 +205,9 @@ "from cognee.shared.utils import render_graph\n", "\n", "# Get graph\n", - "graphistry.login(username=os.getenv(\"GRAPHISTRY_USERNAME\"), password=os.getenv(\"GRAPHISTRY_PASSWORD\"))\n", + "graphistry.login(\n", + " username=os.getenv(\"GRAPHISTRY_USERNAME\"), password=os.getenv(\"GRAPHISTRY_PASSWORD\")\n", + ")\n", "graph_engine = await get_graph_engine()\n", "\n", "graph_url = await render_graph(graph_engine.graph)\n", diff --git a/notebooks/cognee_multimedia_demo.ipynb b/notebooks/cognee_multimedia_demo.ipynb index 2d35132f6..d373b5bd9 100644 --- a/notebooks/cognee_multimedia_demo.ipynb +++ b/notebooks/cognee_multimedia_demo.ipynb @@ -32,11 +32,13 @@ "# cognee knowledge graph will be created based on the text\n", "# and description of these files\n", "mp3_file_path = os.path.join(\n", - " os.path.abspath(''), \"../\",\n", + " os.path.abspath(\"\"),\n", + " \"../\",\n", " \".data/multimedia/text_to_speech.mp3\",\n", ")\n", "png_file_path = os.path.join(\n", - " os.path.abspath(''), \"../\",\n", + " os.path.abspath(\"\"),\n", + " \"../\",\n", " \".data/multimedia/example.png\",\n", ")" ] @@ -57,33 +59,33 @@ "import os\n", "\n", "# Setting environment variables\n", - "if \"GRAPHISTRY_USERNAME\" not in os.environ: \n", + "if \"GRAPHISTRY_USERNAME\" not in os.environ:\n", " os.environ[\"GRAPHISTRY_USERNAME\"] = \"\"\n", "\n", - "if \"GRAPHISTRY_PASSWORD\" not in os.environ: \n", + "if \"GRAPHISTRY_PASSWORD\" not in os.environ:\n", " os.environ[\"GRAPHISTRY_PASSWORD\"] = \"\"\n", "\n", "if \"LLM_API_KEY\" not in os.environ:\n", " os.environ[\"LLM_API_KEY\"] = \"\"\n", "\n", "# \"neo4j\" or \"networkx\"\n", - "os.environ[\"GRAPH_DATABASE_PROVIDER\"]=\"networkx\" \n", + "os.environ[\"GRAPH_DATABASE_PROVIDER\"] = \"networkx\"\n", "# Not needed if using networkx\n", - "#os.environ[\"GRAPH_DATABASE_URL\"]=\"\"\n", - "#os.environ[\"GRAPH_DATABASE_USERNAME\"]=\"\"\n", - "#os.environ[\"GRAPH_DATABASE_PASSWORD\"]=\"\"\n", + "# os.environ[\"GRAPH_DATABASE_URL\"]=\"\"\n", + "# os.environ[\"GRAPH_DATABASE_USERNAME\"]=\"\"\n", + "# os.environ[\"GRAPH_DATABASE_PASSWORD\"]=\"\"\n", "\n", "# \"pgvector\", \"qdrant\", \"weaviate\" or \"lancedb\"\n", - "os.environ[\"VECTOR_DB_PROVIDER\"]=\"lancedb\" \n", + "os.environ[\"VECTOR_DB_PROVIDER\"] = \"lancedb\"\n", "# Not needed if using \"lancedb\" or \"pgvector\"\n", "# os.environ[\"VECTOR_DB_URL\"]=\"\"\n", "# os.environ[\"VECTOR_DB_KEY\"]=\"\"\n", "\n", "# Relational Database provider \"sqlite\" or \"postgres\"\n", - "os.environ[\"DB_PROVIDER\"]=\"sqlite\"\n", + "os.environ[\"DB_PROVIDER\"] = \"sqlite\"\n", "\n", "# Database name\n", - "os.environ[\"DB_NAME\"]=\"cognee_db\"\n", + "os.environ[\"DB_NAME\"] = \"cognee_db\"\n", "\n", "# Postgres specific parameters (Only if Postgres or PGVector is used)\n", "# os.environ[\"DB_HOST\"]=\"127.0.0.1\"\n", diff --git a/notebooks/hr_demo.ipynb b/notebooks/hr_demo.ipynb index ad3fc09d6..c6c3c3ed1 100644 --- a/notebooks/hr_demo.ipynb +++ b/notebooks/hr_demo.ipynb @@ -60,7 +60,7 @@ "Experience with deep learning frameworks (e.g., TensorFlow, PyTorch).\n", "Strong problem-solving skills and attention to detail.\n", "Candidate CVs\n", - "\"\"\"\n" + "\"\"\"" ] }, { @@ -298,33 +298,33 @@ "import os\n", "\n", "# Setting environment variables\n", - "if \"GRAPHISTRY_USERNAME\" not in os.environ: \n", + "if \"GRAPHISTRY_USERNAME\" not in os.environ:\n", " os.environ[\"GRAPHISTRY_USERNAME\"] = \"\"\n", "\n", - "if \"GRAPHISTRY_PASSWORD\" not in os.environ: \n", + "if \"GRAPHISTRY_PASSWORD\" not in os.environ:\n", " os.environ[\"GRAPHISTRY_PASSWORD\"] = \"\"\n", "\n", "if \"LLM_API_KEY\" not in os.environ:\n", " os.environ[\"LLM_API_KEY\"] = \"\"\n", "\n", "# \"neo4j\" or \"networkx\"\n", - "os.environ[\"GRAPH_DATABASE_PROVIDER\"]=\"networkx\" \n", + "os.environ[\"GRAPH_DATABASE_PROVIDER\"] = \"networkx\"\n", "# Not needed if using networkx\n", - "#os.environ[\"GRAPH_DATABASE_URL\"]=\"\"\n", - "#os.environ[\"GRAPH_DATABASE_USERNAME\"]=\"\"\n", - "#os.environ[\"GRAPH_DATABASE_PASSWORD\"]=\"\"\n", + "# os.environ[\"GRAPH_DATABASE_URL\"]=\"\"\n", + "# os.environ[\"GRAPH_DATABASE_USERNAME\"]=\"\"\n", + "# os.environ[\"GRAPH_DATABASE_PASSWORD\"]=\"\"\n", "\n", "# \"pgvector\", \"qdrant\", \"weaviate\" or \"lancedb\"\n", - "os.environ[\"VECTOR_DB_PROVIDER\"]=\"lancedb\" \n", + "os.environ[\"VECTOR_DB_PROVIDER\"] = \"lancedb\"\n", "# Not needed if using \"lancedb\" or \"pgvector\"\n", "# os.environ[\"VECTOR_DB_URL\"]=\"\"\n", "# os.environ[\"VECTOR_DB_KEY\"]=\"\"\n", "\n", "# Relational Database provider \"sqlite\" or \"postgres\"\n", - "os.environ[\"DB_PROVIDER\"]=\"sqlite\"\n", + "os.environ[\"DB_PROVIDER\"] = \"sqlite\"\n", "\n", "# Database name\n", - "os.environ[\"DB_NAME\"]=\"cognee_db\"\n", + "os.environ[\"DB_NAME\"] = \"cognee_db\"\n", "\n", "# Postgres specific parameters (Only if Postgres or PGVector is used)\n", "# os.environ[\"DB_HOST\"]=\"127.0.0.1\"\n", @@ -400,28 +400,35 @@ "from cognee.modules.pipelines.tasks.Task import Task\n", "from cognee.modules.pipelines import run_tasks\n", "from cognee.modules.users.models import User\n", - "from cognee.tasks.documents import check_permissions_on_documents, classify_documents, extract_chunks_from_documents\n", + "from cognee.tasks.documents import (\n", + " check_permissions_on_documents,\n", + " classify_documents,\n", + " extract_chunks_from_documents,\n", + ")\n", "from cognee.tasks.graph import extract_graph_from_data\n", "from cognee.tasks.storage import add_data_points\n", "from cognee.tasks.summarization import summarize_text\n", "\n", + "\n", "async def run_cognify_pipeline(dataset: Dataset, user: User = None):\n", - " data_documents: list[Data] = await get_dataset_data(dataset_id = dataset.id)\n", + " data_documents: list[Data] = await get_dataset_data(dataset_id=dataset.id)\n", "\n", " try:\n", " cognee_config = get_cognify_config()\n", "\n", " tasks = [\n", " Task(classify_documents),\n", - " Task(check_permissions_on_documents, user = user, permissions = [\"write\"]),\n", - " Task(extract_chunks_from_documents), # Extract text chunks based on the document type.\n", - " Task(extract_graph_from_data, graph_model = KnowledgeGraph, task_config = { \"batch_size\": 10 }), # Generate knowledge graphs from the document chunks.\n", + " Task(check_permissions_on_documents, user=user, permissions=[\"write\"]),\n", + " Task(extract_chunks_from_documents), # Extract text chunks based on the document type.\n", + " Task(\n", + " extract_graph_from_data, graph_model=KnowledgeGraph, task_config={\"batch_size\": 10}\n", + " ), # Generate knowledge graphs from the document chunks.\n", " Task(\n", " summarize_text,\n", - " summarization_model = cognee_config.summarization_model,\n", - " task_config = { \"batch_size\": 10 }\n", + " summarization_model=cognee_config.summarization_model,\n", + " task_config={\"batch_size\": 10},\n", " ),\n", - " Task(add_data_points, task_config = { \"batch_size\": 10 }),\n", + " Task(add_data_points, task_config={\"batch_size\": 10}),\n", " ]\n", "\n", " pipeline = run_tasks(tasks, data_documents)\n", @@ -429,7 +436,7 @@ " async for result in pipeline:\n", " print(result)\n", " except Exception as error:\n", - " raise error\n" + " raise error" ] }, { @@ -474,7 +481,9 @@ "from cognee.infrastructure.databases.graph import get_graph_engine\n", "import graphistry\n", "\n", - "graphistry.login(username=os.getenv(\"GRAPHISTRY_USERNAME\"), password=os.getenv(\"GRAPHISTRY_PASSWORD\"))\n", + "graphistry.login(\n", + " username=os.getenv(\"GRAPHISTRY_USERNAME\"), password=os.getenv(\"GRAPHISTRY_PASSWORD\")\n", + ")\n", "\n", "graph_engine = await get_graph_engine()\n", "\n", @@ -511,11 +520,14 @@ "\n", " result_values = list(results.to_dict(\"index\").values())\n", "\n", - " return [dict(\n", - " id = str(result[\"id\"]),\n", - " payload = result[\"payload\"],\n", - " score = result[\"_distance\"],\n", - " ) for result in result_values]\n", + " return [\n", + " dict(\n", + " id=str(result[\"id\"]),\n", + " payload=result[\"payload\"],\n", + " score=result[\"_distance\"],\n", + " )\n", + " for result in result_values\n", + " ]\n", "\n", "\n", "from cognee.infrastructure.databases.vector import get_vector_engine\n", @@ -554,7 +566,7 @@ "node = (await vector_engine.search(\"entity_name\", \"sarah.nguyen@example.com\"))[0]\n", "node_name = node.payload[\"text\"]\n", "\n", - "search_results = await cognee.search(SearchType.SUMMARIES, query_text = node_name)\n", + "search_results = await cognee.search(SearchType.SUMMARIES, query_text=node_name)\n", "print(\"\\n\\Extracted summaries are:\\n\")\n", "for result in search_results:\n", " print(f\"{result}\\n\")" @@ -575,7 +587,7 @@ "metadata": {}, "outputs": [], "source": [ - "search_results = await cognee.search(SearchType.CHUNKS, query_text = node_name)\n", + "search_results = await cognee.search(SearchType.CHUNKS, query_text=node_name)\n", "print(\"\\n\\nExtracted chunks are:\\n\")\n", "for result in search_results:\n", " print(f\"{result}\\n\")" @@ -596,26 +608,42 @@ "metadata": {}, "outputs": [], "source": [ - "search_results = await cognee.search(SearchType.INSIGHTS, query_text = node_name)\n", + "search_results = await cognee.search(SearchType.INSIGHTS, query_text=node_name)\n", "print(\"\\n\\nExtracted sentences are:\\n\")\n", "for result in search_results:\n", " print(f\"{result}\\n\")" ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "## Let's add evals", - "id": "e519e30c0423c2a" + "id": "e519e30c0423c2a", + "metadata": {}, + "source": "## Let's add evals" }, { + "cell_type": "code", + "execution_count": 3, + "id": "b22ae3d868fa5606", "metadata": { "ExecuteTime": { "end_time": "2024-12-19T18:01:11.387716Z", "start_time": "2024-12-19T18:01:11.278042Z" } }, - "cell_type": "code", + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'deepeval'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[3], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mevals\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01meval_on_hotpot\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m eval_on_hotpotQA\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mevals\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01meval_on_hotpot\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m answer_with_cognee\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mevals\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01meval_on_hotpot\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m answer_without_cognee\n", + "File \u001b[0;32m~/cognee/evals/eval_on_hotpot.py:7\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mstatistics\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpathlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Path\n\u001b[0;32m----> 7\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mdeepeval\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mmetrics\u001b[39;00m\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mwget\u001b[39;00m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mdeepeval\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mdataset\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m EvaluationDataset\n", + "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'deepeval'" + ] + } + ], "source": [ "from evals.eval_on_hotpot import eval_on_hotpotQA\n", "from evals.eval_on_hotpot import answer_with_cognee\n", @@ -626,33 +654,18 @@ "from tqdm import tqdm\n", "import wget\n", "import json\n", - "import statistics\n" - ], - "id": "b22ae3d868fa5606", - "outputs": [ - { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'deepeval'", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mModuleNotFoundError\u001B[0m Traceback (most recent call last)", - "Cell \u001B[0;32mIn[3], line 1\u001B[0m\n\u001B[0;32m----> 1\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mevals\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01meval_on_hotpot\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m eval_on_hotpotQA\n\u001B[1;32m 2\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mevals\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01meval_on_hotpot\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m answer_with_cognee\n\u001B[1;32m 3\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mevals\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01meval_on_hotpot\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m answer_without_cognee\n", - "File \u001B[0;32m~/cognee/evals/eval_on_hotpot.py:7\u001B[0m\n\u001B[1;32m 4\u001B[0m \u001B[38;5;28;01mimport\u001B[39;00m \u001B[38;5;21;01mstatistics\u001B[39;00m\n\u001B[1;32m 5\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mpathlib\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m Path\n\u001B[0;32m----> 7\u001B[0m \u001B[38;5;28;01mimport\u001B[39;00m \u001B[38;5;21;01mdeepeval\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mmetrics\u001B[39;00m\n\u001B[1;32m 8\u001B[0m \u001B[38;5;28;01mimport\u001B[39;00m \u001B[38;5;21;01mwget\u001B[39;00m\n\u001B[1;32m 9\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mdeepeval\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mdataset\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m EvaluationDataset\n", - "\u001B[0;31mModuleNotFoundError\u001B[0m: No module named 'deepeval'" - ] - } - ], - "execution_count": 3 + "import statistics" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "728355d390e3a01b", + "metadata": {}, + "outputs": [], "source": [ - "answer_provider = answer_with_cognee # For native LLM answers use answer_without_cognee\n", - "num_samples = 10 # With cognee, it takes ~1m10s per sample\n", + "answer_provider = answer_with_cognee # For native LLM answers use answer_without_cognee\n", + "num_samples = 10 # With cognee, it takes ~1m10s per sample\n", "\n", "base_config = get_base_config()\n", "data_root_dir = base_config.data_root_directory\n", @@ -662,7 +675,7 @@ "\n", "filepath = data_root_dir / Path(\"hotpot_dev_fullwiki_v1.json\")\n", "if not filepath.exists():\n", - " url = 'http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json'\n", + " url = \"http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json\"\n", " wget.download(url, out=data_root_dir)\n", "\n", "with open(filepath, \"r\") as file:\n", @@ -673,8 +686,7 @@ "for instance in tqdm(instances, desc=\"Getting answers\"):\n", " answer = answer_provider(instance)\n", " answers.append(answer)" - ], - "id": "728355d390e3a01b" + ] }, { "cell_type": "markdown", diff --git a/poetry.lock b/poetry.lock index 989ad440b..fe94fd245 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aiofiles" @@ -223,13 +223,13 @@ vertex = ["google-auth (>=2,<3)"] [[package]] name = "anyio" -version = "4.7.0" +version = "4.8.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" files = [ - {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, - {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, ] [package.dependencies] @@ -240,7 +240,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -332,13 +332,13 @@ test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock [[package]] name = "astroid" -version = "3.3.6" +version = "3.3.8" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" files = [ - {file = "astroid-3.3.6-py3-none-any.whl", hash = "sha256:db676dc4f3ae6bfe31cda227dc60e03438378d7a896aec57422c95634e8d722f"}, - {file = "astroid-3.3.6.tar.gz", hash = "sha256:6aaea045f938c735ead292204afdb977a36e989522b7833ef6fea94de743f442"}, + {file = "astroid-3.3.8-py3-none-any.whl", hash = "sha256:187ccc0c248bfbba564826c26f070494f7bc964fd286b6d9fff4420e55de828c"}, + {file = "astroid-3.3.8.tar.gz", hash = "sha256:a88c7994f914a4ea8572fac479459f4955eeccc877be3f2d959a33273b0cf40b"}, ] [package.dependencies] @@ -579,24 +579,47 @@ files = [ ] [package.dependencies] +tinycss2 = {version = ">=1.1.0,<1.5", optional = true, markers = "extra == \"css\""} webencodings = "*" [package.extras] css = ["tinycss2 (>=1.1.0,<1.5)"] +[[package]] +name = "bokeh" +version = "3.6.2" +description = "Interactive plots and applications in the browser from Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "bokeh-3.6.2-py3-none-any.whl", hash = "sha256:fddc4b91f8b40178c0e3e83dfcc33886d7803a3a1f041a840834255e435a18c2"}, + {file = "bokeh-3.6.2.tar.gz", hash = "sha256:2f3043d9ecb3d5dc2e8c0ebf8ad55727617188d4e534f3e7208b36357e352396"}, +] + +[package.dependencies] +contourpy = ">=1.2" +Jinja2 = ">=2.9" +numpy = ">=1.16" +packaging = ">=16.8" +pandas = ">=1.2" +pillow = ">=7.1.0" +PyYAML = ">=3.10" +tornado = ">=6.2" +xyzservices = ">=2021.09.1" + [[package]] name = "boto3" -version = "1.35.85" +version = "1.35.94" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.85-py3-none-any.whl", hash = "sha256:f22678bdbdc91ca6022a45696284d236e1fbafa84ca3a69d108d4a155cdd823e"}, - {file = "boto3-1.35.85.tar.gz", hash = "sha256:6257cad97d92c2b5597aec6e5484b9cfed8c0c785297942ed37cfaf2dd0ec23c"}, + {file = "boto3-1.35.94-py3-none-any.whl", hash = "sha256:516c514fb447d6f216833d06a0781c003fcf43099a4ca2f5a363a8afe0942070"}, + {file = "boto3-1.35.94.tar.gz", hash = "sha256:5aa606239f0fe0dca0506e0ad6bbe4c589048e7e6c2486cee5ec22b6aa7ec2f8"}, ] [package.dependencies] -botocore = ">=1.35.85,<1.36.0" +botocore = ">=1.35.94,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -605,37 +628,23 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.35.85" +version = "1.35.94" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.85-py3-none-any.whl", hash = "sha256:04c196905b0eebcb29f7594a9e4588772a5222deed1b381f54cab78d0f30e239"}, - {file = "botocore-1.35.85.tar.gz", hash = "sha256:5e7e8075e85427c9e0e6d15dcb7d13b3c843011b25d43981571fe1bfb3fd6985"}, + {file = "botocore-1.35.94-py3-none-any.whl", hash = "sha256:d784d944865d8279c79d2301fc09ac28b5221d4e7328fb4e23c642c253b9932c"}, + {file = "botocore-1.35.94.tar.gz", hash = "sha256:2b3309b356541faa4d88bb957dcac1d8004aa44953c0b7d4521a6cc5d3d5d6ba"}, ] [package.dependencies] jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" -urllib3 = [ - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, - {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, -] +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] crt = ["awscrt (==0.22.0)"] -[[package]] -name = "cachetools" -version = "5.5.0" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -files = [ - {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, - {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, -] - [[package]] name = "certifi" version = "2024.12.14" @@ -750,127 +759,114 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.0" +version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -906,76 +902,65 @@ test = ["pytest"] [[package]] name = "contourpy" -version = "1.3.0" +version = "1.3.1" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false -python-versions = ">=3.9" -files = [ - {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"}, +python-versions = ">=3.10" +files = [ + {file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab"}, + {file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595"}, + {file = "contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697"}, + {file = "contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f"}, + {file = "contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375"}, + {file = "contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d"}, + {file = "contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e"}, + {file = "contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1"}, + {file = "contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82"}, + {file = "contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53"}, + {file = "contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699"}, ] [package.dependencies] @@ -990,73 +975,73 @@ test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist" [[package]] name = "coverage" -version = "7.6.9" +version = "7.6.10" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, - {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, - {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"}, - {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"}, - {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"}, - {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"}, - {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"}, - {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"}, - {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"}, - {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"}, - {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"}, - {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"}, - {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"}, - {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"}, - {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"}, - {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"}, - {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"}, - {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"}, - {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"}, - {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"}, - {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"}, - {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"}, - {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"}, - {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"}, - {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"}, - {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"}, - {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"}, - {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"}, - {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"}, - {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"}, - {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"}, - {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"}, - {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"}, - {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"}, - {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"}, - {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"}, - {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"}, - {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"}, - {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"}, - {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"}, - {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"}, - {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"}, - {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"}, - {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"}, - {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"}, - {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"}, - {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"}, - {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"}, - {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"}, - {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"}, - {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"}, - {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"}, - {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"}, - {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"}, - {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"}, - {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"}, - {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"}, - {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"}, - {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"}, - {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"}, - {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"}, - {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, + {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, + {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, + {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, + {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, + {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, + {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, + {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, + {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, + {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, + {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, + {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, + {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, + {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, + {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, ] [package.extras] @@ -1064,51 +1049,51 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "43.0.3" +version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, - {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, - {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, - {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, - {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, - {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, - {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +files = [ + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -1259,13 +1244,13 @@ optimize = ["orjson"] [[package]] name = "deepeval" -version = "2.0.8" +version = "2.1.1" description = "The Open-Source LLM Evaluation Framework." optional = true python-versions = "<3.13,>=3.9" files = [ - {file = "deepeval-2.0.8-py3-none-any.whl", hash = "sha256:a947f7440f168e734b3b433b6b2c56c512757cfcc403aba99c6393a45a15f776"}, - {file = "deepeval-2.0.8.tar.gz", hash = "sha256:a0222f93f9a50d51b9962d88d16c2fc315e8223b756355e65eb9a1d5e1a8ae40"}, + {file = "deepeval-2.1.1-py3-none-any.whl", hash = "sha256:6b3c48c184ed7d904bea379427f62aad8bbf50ddfa493957d8adb160520ab429"}, + {file = "deepeval-2.1.1.tar.gz", hash = "sha256:8554b7abad140c4948d5ae33670beaabd7c3c969cd216ead4ef74268032d4f2d"}, ] [package.dependencies] @@ -1641,39 +1626,39 @@ standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "htt [[package]] name = "fastapi-users" -version = "14.0.0" +version = "14.0.1" description = "Ready-to-use and customizable users management for FastAPI" optional = false python-versions = ">=3.9" files = [ - {file = "fastapi_users-14.0.0-py3-none-any.whl", hash = "sha256:e1230e044ddc2209b890b5b5c6fc1d13def961298d40e01c2d28f8bc2fe8c4c7"}, - {file = "fastapi_users-14.0.0.tar.gz", hash = "sha256:6dceefbd2db87a17f791ef431d616bb5ce40cb123da7922969b704cbee5e7384"}, + {file = "fastapi_users-14.0.1-py3-none-any.whl", hash = "sha256:074df59676dccf79412d2880bdcb661ab1fabc2ecec1f043b4e6a23be97ed9e1"}, + {file = "fastapi_users-14.0.1.tar.gz", hash = "sha256:8c032b3a75c6fb2b1f5eab8ffce5321176e9916efe1fe93e7c15ee55f0b02236"}, ] [package.dependencies] email-validator = ">=1.1.0,<2.3" fastapi = ">=0.65.2" -fastapi-users-db-sqlalchemy = {version = ">=6.0.0", optional = true, markers = "extra == \"sqlalchemy\""} +fastapi-users-db-sqlalchemy = {version = ">=7.0.0", optional = true, markers = "extra == \"sqlalchemy\""} makefun = ">=1.11.2,<2.0.0" pwdlib = {version = "0.2.1", extras = ["argon2", "bcrypt"]} -pyjwt = {version = "2.9.0", extras = ["crypto"]} -python-multipart = "0.0.17" +pyjwt = {version = "2.10.1", extras = ["crypto"]} +python-multipart = "0.0.20" [package.extras] -beanie = ["fastapi-users-db-beanie (>=3.0.0)"] +beanie = ["fastapi-users-db-beanie (>=4.0.0)"] oauth = ["httpx-oauth (>=0.13)"] redis = ["redis (>=4.3.3,<6.0.0)"] -sqlalchemy = ["fastapi-users-db-sqlalchemy (>=6.0.0)"] +sqlalchemy = ["fastapi-users-db-sqlalchemy (>=7.0.0)"] [[package]] name = "fastapi-users-db-sqlalchemy" -version = "6.0.1" +version = "7.0.0" description = "FastAPI Users database adapter for SQLAlchemy" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "fastapi_users_db_sqlalchemy-6.0.1-py3-none-any.whl", hash = "sha256:d1050ec31eb75e8c4fa9abafa4addaf0baf5c97afeea2f0f910ea55e2451fcad"}, - {file = "fastapi_users_db_sqlalchemy-6.0.1.tar.gz", hash = "sha256:f0ef9fe3250453712d25c13170700c80fa205867ce7add7ef391c384ec27cbe1"}, + {file = "fastapi_users_db_sqlalchemy-7.0.0-py3-none-any.whl", hash = "sha256:5fceac018e7cfa69efc70834dd3035b3de7988eb4274154a0dbe8b14f5aa001e"}, + {file = "fastapi_users_db_sqlalchemy-7.0.0.tar.gz", hash = "sha256:6823eeedf8a92f819276a2b2210ef1dcfd71fe8b6e37f7b4da8d1c60e3dfd595"}, ] [package.dependencies] @@ -1967,13 +1952,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "gitdb" -version = "4.0.11" +version = "4.0.12" description = "Git Object Database" optional = false python-versions = ">=3.7" files = [ - {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, - {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, + {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, + {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, ] [package.dependencies] @@ -1981,20 +1966,20 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.43" +version = "3.1.44" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, - {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, + {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, + {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] +doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] [[package]] @@ -2151,13 +2136,13 @@ test = ["objgraph", "psutil"] [[package]] name = "griffe" -version = "1.5.1" +version = "1.5.4" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.9" files = [ - {file = "griffe-1.5.1-py3-none-any.whl", hash = "sha256:ad6a7980f8c424c9102160aafa3bcdf799df0e75f7829d75af9ee5aef656f860"}, - {file = "griffe-1.5.1.tar.gz", hash = "sha256:72964f93e08c553257706d6cd2c42d1c172213feb48b2be386f243380b405d4b"}, + {file = "griffe-1.5.4-py3-none-any.whl", hash = "sha256:ed33af890586a5bebc842fcb919fc694b3dc1bc55b7d9e0228de41ce566b4a1d"}, + {file = "griffe-1.5.4.tar.gz", hash = "sha256:073e78ad3e10c8378c2f798bd4ef87b92d8411e9916e157fd366a17cc4fd4e52"}, ] [package.dependencies] @@ -2494,13 +2479,13 @@ files = [ [[package]] name = "huggingface-hub" -version = "0.27.0" +version = "0.27.1" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.27.0-py3-none-any.whl", hash = "sha256:8f2e834517f1f1ddf1ecc716f91b120d7333011b7485f665a9a412eacb1a2a81"}, - {file = "huggingface_hub-0.27.0.tar.gz", hash = "sha256:902cce1a1be5739f5589e560198a65a8edcfd3b830b1666f36e4b961f0454fac"}, + {file = "huggingface_hub-0.27.1-py3-none-any.whl", hash = "sha256:1c5155ca7d60b60c2e2fc38cbb3ffb7f7c3adf48f824015b219af9061771daec"}, + {file = "huggingface_hub-0.27.1.tar.gz", hash = "sha256:c004463ca870283909d715d20f066ebd6968c2207dae9393fdffb3c1d4d8f98b"}, ] [package.dependencies] @@ -2553,13 +2538,13 @@ files = [ [[package]] name = "identify" -version = "2.6.3" +version = "2.6.5" description = "File identification library for Python" optional = false python-versions = ">=3.9" files = [ - {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"}, - {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"}, + {file = "identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566"}, + {file = "identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc"}, ] [package.extras] @@ -2598,28 +2583,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.5" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, - {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, -] - -[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" @@ -2633,36 +2596,38 @@ files = [ [[package]] name = "instructor" -version = "1.5.2" +version = "1.7.2" description = "structured outputs for llm" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "instructor-1.5.2-py3-none-any.whl", hash = "sha256:da25abbf1ab792fb094992f1d9ce593e26fe458cb1f9a8e7ebf9d68f3f2c757a"}, - {file = "instructor-1.5.2.tar.gz", hash = "sha256:fdd5ccbca21b4c558a24e9ba12c84afd907e65153a39d035f47c25800011a977"}, + {file = "instructor-1.7.2-py3-none-any.whl", hash = "sha256:cb43d27f6d7631c31762b936b2fcb44d2a3f9d8a020430a0f4d3484604ffb95b"}, + {file = "instructor-1.7.2.tar.gz", hash = "sha256:6c01b2b159766df24865dc81f7bf8457cbda88a3c0bbc810da3467d19b185ed2"}, ] [package.dependencies] aiohttp = ">=3.9.1,<4.0.0" -docstring-parser = ">=0.16,<0.17" -jiter = ">=0.5.0,<0.6.0" -openai = ">=1.45.0,<2.0.0" +docstring-parser = ">=0.16,<1.0" +jinja2 = ">=3.1.4,<4.0.0" +jiter = ">=0.6.1,<0.9" +openai = ">=1.52.0,<2.0.0" pydantic = ">=2.8.0,<3.0.0" pydantic-core = ">=2.18.0,<3.0.0" +requests = ">=2.32.3,<3.0.0" rich = ">=13.7.0,<14.0.0" -tenacity = ">=8.4.1,<9.0.0" +tenacity = ">=9.0.0,<10.0.0" typer = ">=0.9.0,<1.0.0" [package.extras] -anthropic = ["anthropic (>=0.34.0,<0.35.0)", "xmltodict (>=0.13.0,<0.14.0)"] -cerebras-cloud-sdk = ["cerebras_cloud_sdk (>=1.5.0,<2.0.0)"] +anthropic = ["anthropic (==0.42.0)", "xmltodict (>=0.13,<0.15)"] +cerebras-cloud-sdk = ["cerebras-cloud-sdk (>=1.5.0,<2.0.0)"] cohere = ["cohere (>=5.1.8,<6.0.0)"] -google-generativeai = ["google-generativeai (>=0.8.2,<0.9.0)"] -groq = ["groq (>=0.4.2,<0.5.0)"] -litellm = ["litellm (>=1.35.31,<2.0.0)"] -mistralai = ["mistralai (>=1.0.3,<2.0.0)"] -test-docs = ["anthropic (>=0.34.0,<0.35.0)", "cohere (>=5.1.8,<6.0.0)", "diskcache (>=5.6.3,<6.0.0)", "fastapi (>=0.109.2,<0.110.0)", "groq (>=0.4.2,<0.5.0)", "litellm (>=1.35.31,<2.0.0)", "mistralai (>=1.0.3,<2.0.0)", "pandas (>=2.2.0,<3.0.0)", "pydantic_extra_types (>=2.6.0,<3.0.0)", "redis (>=5.0.1,<6.0.0)", "tabulate (>=0.9.0,<0.10.0)"] +fireworks-ai = ["fireworks-ai (>=0.15.4,<1.0.0)"] +google-generativeai = ["google-generativeai (>=0.8.2,<1.0.0)", "jsonref (>=1.1.0,<2.0.0)"] +groq = ["groq (>=0.4.2,<0.14.0)"] +test-docs = ["diskcache (>=5.6.3,<6.0.0)", "fastapi (>=0.109.2,<0.116.0)", "litellm (>=1.35.31,<2.0.0)", "mistralai (>=1.0.3,<2.0.0)", "pandas (>=2.2.0,<3.0.0)", "pydantic-extra-types (>=2.6.0,<3.0.0)", "redis (>=5.0.1,<6.0.0)", "tabulate (>=0.9.0,<1.0.0)"] vertexai = ["google-cloud-aiplatform (>=1.53.0,<2.0.0)", "jsonref (>=1.1.0,<2.0.0)"] +writer = ["writer-sdk (>=1.2.0,<2.0.0)"] [[package]] name = "ipykernel" @@ -2699,13 +2664,13 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio [[package]] name = "ipython" -version = "8.18.1" +version = "8.31.0" description = "IPython: Productive Interactive Computing" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, - {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, + {file = "ipython-8.31.0-py3-none-any.whl", hash = "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6"}, + {file = "ipython-8.31.0.tar.gz", hash = "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b"}, ] [package.dependencies] @@ -2714,25 +2679,26 @@ decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -prompt-toolkit = ">=3.0.41,<3.1.0" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt_toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" -stack-data = "*" -traitlets = ">=5" -typing-extensions = {version = "*", markers = "python_version < \"3.10\""} +stack_data = "*" +traitlets = ">=5.13.0" +typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing_extensions"] kernel = ["ipykernel"] +matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] [[package]] name = "isoduration" @@ -2783,13 +2749,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -2800,72 +2766,87 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jiter" -version = "0.5.0" +version = "0.8.2" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" files = [ - {file = "jiter-0.5.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b599f4e89b3def9a94091e6ee52e1d7ad7bc33e238ebb9c4c63f211d74822c3f"}, - {file = "jiter-0.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a063f71c4b06225543dddadbe09d203dc0c95ba352d8b85f1221173480a71d5"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc0d5b8b3dd12e91dd184b87273f864b363dfabc90ef29a1092d269f18c7e28"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c22541f0b672f4d741382a97c65609332a783501551445ab2df137ada01e019e"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63314832e302cc10d8dfbda0333a384bf4bcfce80d65fe99b0f3c0da8945a91a"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a25fbd8a5a58061e433d6fae6d5298777c0814a8bcefa1e5ecfff20c594bd749"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:503b2c27d87dfff5ab717a8200fbbcf4714516c9d85558048b1fc14d2de7d8dc"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d1f3d27cce923713933a844872d213d244e09b53ec99b7a7fdf73d543529d6d"}, - {file = "jiter-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c95980207b3998f2c3b3098f357994d3fd7661121f30669ca7cb945f09510a87"}, - {file = "jiter-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:afa66939d834b0ce063f57d9895e8036ffc41c4bd90e4a99631e5f261d9b518e"}, - {file = "jiter-0.5.0-cp310-none-win32.whl", hash = "sha256:f16ca8f10e62f25fd81d5310e852df6649af17824146ca74647a018424ddeccf"}, - {file = "jiter-0.5.0-cp310-none-win_amd64.whl", hash = "sha256:b2950e4798e82dd9176935ef6a55cf6a448b5c71515a556da3f6b811a7844f1e"}, - {file = "jiter-0.5.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4c8e1ed0ef31ad29cae5ea16b9e41529eb50a7fba70600008e9f8de6376d553"}, - {file = "jiter-0.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6f16e21276074a12d8421692515b3fd6d2ea9c94fd0734c39a12960a20e85f3"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5280e68e7740c8c128d3ae5ab63335ce6d1fb6603d3b809637b11713487af9e6"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:583c57fc30cc1fec360e66323aadd7fc3edeec01289bfafc35d3b9dcb29495e4"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26351cc14507bdf466b5f99aba3df3143a59da75799bf64a53a3ad3155ecded9"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829df14d656b3fb87e50ae8b48253a8851c707da9f30d45aacab2aa2ba2d614"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42a4bdcf7307b86cb863b2fb9bb55029b422d8f86276a50487982d99eed7c6e"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04d461ad0aebf696f8da13c99bc1b3e06f66ecf6cfd56254cc402f6385231c06"}, - {file = "jiter-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6375923c5f19888c9226582a124b77b622f8fd0018b843c45eeb19d9701c403"}, - {file = "jiter-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cec323a853c24fd0472517113768c92ae0be8f8c384ef4441d3632da8baa646"}, - {file = "jiter-0.5.0-cp311-none-win32.whl", hash = "sha256:aa1db0967130b5cab63dfe4d6ff547c88b2a394c3410db64744d491df7f069bb"}, - {file = "jiter-0.5.0-cp311-none-win_amd64.whl", hash = "sha256:aa9d2b85b2ed7dc7697597dcfaac66e63c1b3028652f751c81c65a9f220899ae"}, - {file = "jiter-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9f664e7351604f91dcdd557603c57fc0d551bc65cc0a732fdacbf73ad335049a"}, - {file = "jiter-0.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:044f2f1148b5248ad2c8c3afb43430dccf676c5a5834d2f5089a4e6c5bbd64df"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:702e3520384c88b6e270c55c772d4bd6d7b150608dcc94dea87ceba1b6391248"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:528d742dcde73fad9d63e8242c036ab4a84389a56e04efd854062b660f559544"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf80e5fe6ab582c82f0c3331df27a7e1565e2dcf06265afd5173d809cdbf9ba"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44dfc9ddfb9b51a5626568ef4e55ada462b7328996294fe4d36de02fce42721f"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c451f7922992751a936b96c5f5b9bb9312243d9b754c34b33d0cb72c84669f4e"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:308fce789a2f093dca1ff91ac391f11a9f99c35369117ad5a5c6c4903e1b3e3a"}, - {file = "jiter-0.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7f5ad4a7c6b0d90776fdefa294f662e8a86871e601309643de30bf94bb93a64e"}, - {file = "jiter-0.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea189db75f8eca08807d02ae27929e890c7d47599ce3d0a6a5d41f2419ecf338"}, - {file = "jiter-0.5.0-cp312-none-win32.whl", hash = "sha256:e3bbe3910c724b877846186c25fe3c802e105a2c1fc2b57d6688b9f8772026e4"}, - {file = "jiter-0.5.0-cp312-none-win_amd64.whl", hash = "sha256:a586832f70c3f1481732919215f36d41c59ca080fa27a65cf23d9490e75b2ef5"}, - {file = "jiter-0.5.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f04bc2fc50dc77be9d10f73fcc4e39346402ffe21726ff41028f36e179b587e6"}, - {file = "jiter-0.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f433a4169ad22fcb550b11179bb2b4fd405de9b982601914ef448390b2954f3"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad4a6398c85d3a20067e6c69890ca01f68659da94d74c800298581724e426c7e"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6baa88334e7af3f4d7a5c66c3a63808e5efbc3698a1c57626541ddd22f8e4fbf"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ece0a115c05efca597c6d938f88c9357c843f8c245dbbb53361a1c01afd7148"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:335942557162ad372cc367ffaf93217117401bf930483b4b3ebdb1223dbddfa7"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649b0ee97a6e6da174bffcb3c8c051a5935d7d4f2f52ea1583b5b3e7822fbf14"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f4be354c5de82157886ca7f5925dbda369b77344b4b4adf2723079715f823989"}, - {file = "jiter-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5206144578831a6de278a38896864ded4ed96af66e1e63ec5dd7f4a1fce38a3a"}, - {file = "jiter-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8120c60f8121ac3d6f072b97ef0e71770cc72b3c23084c72c4189428b1b1d3b6"}, - {file = "jiter-0.5.0-cp38-none-win32.whl", hash = "sha256:6f1223f88b6d76b519cb033a4d3687ca157c272ec5d6015c322fc5b3074d8a5e"}, - {file = "jiter-0.5.0-cp38-none-win_amd64.whl", hash = "sha256:c59614b225d9f434ea8fc0d0bec51ef5fa8c83679afedc0433905994fb36d631"}, - {file = "jiter-0.5.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0af3838cfb7e6afee3f00dc66fa24695199e20ba87df26e942820345b0afc566"}, - {file = "jiter-0.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:550b11d669600dbc342364fd4adbe987f14d0bbedaf06feb1b983383dcc4b961"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:489875bf1a0ffb3cb38a727b01e6673f0f2e395b2aad3c9387f94187cb214bbf"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b250ca2594f5599ca82ba7e68785a669b352156260c5362ea1b4e04a0f3e2389"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ea18e01f785c6667ca15407cd6dabbe029d77474d53595a189bdc813347218e"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:462a52be85b53cd9bffd94e2d788a09984274fe6cebb893d6287e1c296d50653"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92cc68b48d50fa472c79c93965e19bd48f40f207cb557a8346daa020d6ba973b"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c834133e59a8521bc87ebcad773608c6fa6ab5c7a022df24a45030826cf10bc"}, - {file = "jiter-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab3a71ff31cf2d45cb216dc37af522d335211f3a972d2fe14ea99073de6cb104"}, - {file = "jiter-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cccd3af9c48ac500c95e1bcbc498020c87e1781ff0345dd371462d67b76643eb"}, - {file = "jiter-0.5.0-cp39-none-win32.whl", hash = "sha256:368084d8d5c4fc40ff7c3cc513c4f73e02c85f6009217922d0823a48ee7adf61"}, - {file = "jiter-0.5.0-cp39-none-win_amd64.whl", hash = "sha256:ce03f7b4129eb72f1687fa11300fbf677b02990618428934662406d2a76742a1"}, - {file = "jiter-0.5.0.tar.gz", hash = "sha256:1d916ba875bcab5c5f7d927df998c4cb694d27dceddf3392e58beaf10563368a"}, + {file = "jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b"}, + {file = "jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c826a221851a8dc028eb6d7d6429ba03184fa3c7e83ae01cd6d3bd1d4bd17d"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d35c864c2dff13dfd79fb070fc4fc6235d7b9b359efe340e1261deb21b9fcb66"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f557c55bc2b7676e74d39d19bcb8775ca295c7a028246175d6a8b431e70835e5"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:580ccf358539153db147e40751a0b41688a5ceb275e6f3e93d91c9467f42b2e3"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af102d3372e917cffce49b521e4c32c497515119dc7bd8a75665e90a718bbf08"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cadcc978f82397d515bb2683fc0d50103acff2a180552654bb92d6045dec2c49"}, + {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba5bdf56969cad2019d4e8ffd3f879b5fdc792624129741d3d83fc832fef8c7d"}, + {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b94a33a241bee9e34b8481cdcaa3d5c2116f575e0226e421bed3f7a6ea71cff"}, + {file = "jiter-0.8.2-cp310-cp310-win32.whl", hash = "sha256:6e5337bf454abddd91bd048ce0dca5134056fc99ca0205258766db35d0a2ea43"}, + {file = "jiter-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4a9220497ca0cb1fe94e3f334f65b9b5102a0b8147646118f020d8ce1de70105"}, + {file = "jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b"}, + {file = "jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc"}, + {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88"}, + {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6"}, + {file = "jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44"}, + {file = "jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855"}, + {file = "jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f"}, + {file = "jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d"}, + {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152"}, + {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29"}, + {file = "jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e"}, + {file = "jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c"}, + {file = "jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84"}, + {file = "jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1"}, + {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9"}, + {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05"}, + {file = "jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a"}, + {file = "jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865"}, + {file = "jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca"}, + {file = "jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0"}, + {file = "jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566"}, + {file = "jiter-0.8.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9e1fa156ee9454642adb7e7234a383884452532bc9d53d5af2d18d98ada1d79c"}, + {file = "jiter-0.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cf5dfa9956d96ff2efb0f8e9c7d055904012c952539a774305aaaf3abdf3d6c"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e52bf98c7e727dd44f7c4acb980cb988448faeafed8433c867888268899b298b"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a2ecaa3c23e7a7cf86d00eda3390c232f4d533cd9ddea4b04f5d0644faf642c5"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:08d4c92bf480e19fc3f2717c9ce2aa31dceaa9163839a311424b6862252c943e"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d9a1eded738299ba8e106c6779ce5c3893cffa0e32e4485d680588adae6db8"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20be8b7f606df096e08b0b1b4a3c6f0515e8dac296881fe7461dfa0fb5ec817"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d33f94615fcaf872f7fd8cd98ac3b429e435c77619777e8a449d9d27e01134d1"}, + {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:317b25e98a35ffec5c67efe56a4e9970852632c810d35b34ecdd70cc0e47b3b6"}, + {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc9043259ee430ecd71d178fccabd8c332a3bf1e81e50cae43cc2b28d19e4cb7"}, + {file = "jiter-0.8.2-cp38-cp38-win32.whl", hash = "sha256:fc5adda618205bd4678b146612ce44c3cbfdee9697951f2c0ffdef1f26d72b63"}, + {file = "jiter-0.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cd646c827b4f85ef4a78e4e58f4f5854fae0caf3db91b59f0d73731448a970c6"}, + {file = "jiter-0.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e41e75344acef3fc59ba4765df29f107f309ca9e8eace5baacabd9217e52a5ee"}, + {file = "jiter-0.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f22b16b35d5c1df9dfd58843ab2cd25e6bf15191f5a236bed177afade507bfc"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7200b8f7619d36aa51c803fd52020a2dfbea36ffec1b5e22cab11fd34d95a6d"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70bf4c43652cc294040dbb62256c83c8718370c8b93dd93d934b9a7bf6c4f53c"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9d471356dc16f84ed48768b8ee79f29514295c7295cb41e1133ec0b2b8d637d"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:859e8eb3507894093d01929e12e267f83b1d5f6221099d3ec976f0c995cb6bd9"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaa58399c01db555346647a907b4ef6d4f584b123943be6ed5588c3f2359c9f4"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f2d5ed877f089862f4c7aacf3a542627c1496f972a34d0474ce85ee7d939c27"}, + {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:03c9df035d4f8d647f8c210ddc2ae0728387275340668fb30d2421e17d9a0841"}, + {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8bd2a824d08d8977bb2794ea2682f898ad3d8837932e3a74937e93d62ecbb637"}, + {file = "jiter-0.8.2-cp39-cp39-win32.whl", hash = "sha256:ca29b6371ebc40e496995c94b988a101b9fbbed48a51190a4461fcb0a68b4a36"}, + {file = "jiter-0.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:1c0dfbd1be3cbefc7510102370d86e35d1d53e5a93d48519688b1bf0f761160a"}, + {file = "jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d"}, ] [[package]] @@ -2936,6 +2917,8 @@ optional = false python-versions = "*" files = [ {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, + {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, + {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, ] [package.dependencies] @@ -3018,7 +3001,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" python-dateutil = ">=2.8.2" pyzmq = ">=23.0" @@ -3086,7 +3068,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} jupyter-server = ">=1.1.2" [[package]] @@ -3158,7 +3139,6 @@ files = [ [package.dependencies] async-lru = ">=1.0.0" httpx = ">=0.25.0" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} ipykernel = ">=6.5.0" jinja2 = ">=3.0.3" jupyter-core = "*" @@ -3203,7 +3183,6 @@ files = [ [package.dependencies] babel = ">=2.10" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} jinja2 = ">=3.0.3" json5 = ">=0.9.0" jsonschema = ">=4.18.0" @@ -3218,151 +3197,115 @@ test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-v [[package]] name = "kiwisolver" -version = "1.4.7" +version = "1.4.8" description = "A fast implementation of the Cassowary constraint solver" optional = false -python-versions = ">=3.8" -files = [ - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, - {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, - {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, - {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, +python-versions = ">=3.10" +files = [ + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b"}, + {file = "kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e"}, ] [[package]] name = "lancedb" -version = "0.15.0" +version = "0.16.0" description = "lancedb" optional = false python-versions = ">=3.9" files = [ - {file = "lancedb-0.15.0-cp38-abi3-macosx_10_15_x86_64.whl", hash = "sha256:3eacc9c6766594874a7d54e822550c7991d64b14571251f1e4b43985cc4f3cdb"}, - {file = "lancedb-0.15.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:48c28571f79805e11a3bca09486fd1c8d6c3f7762f7692cca1c5e0cdea6bfa20"}, - {file = "lancedb-0.15.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e349a1671943b75a536d2589b5a12f685c129149b0cad266e12555f9501f8ccd"}, - {file = "lancedb-0.15.0-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:c567866b08222457e1aca51df9abeb871aad8fed0db58c004365629c05f8ecbb"}, - {file = "lancedb-0.15.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:223cd77fa84a1317301ad4771de58ac5685d58cee03f0a20ba4bc95517b5c79f"}, - {file = "lancedb-0.15.0-cp38-abi3-win_amd64.whl", hash = "sha256:66d251f22709c72f819aace9e665127f1040845d88b25c1f088c4beb36087f7e"}, + {file = "lancedb-0.16.0-cp38-abi3-macosx_10_15_x86_64.whl", hash = "sha256:3521c53a116bfbb054318a35b2297cd01d57e1db500de4ba3cc7fad6c4add98c"}, + {file = "lancedb-0.16.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e0968e6b7a3611437dc4c4f468aafb4e665aa315ee0b201e589ea1fa433b5b6"}, + {file = "lancedb-0.16.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e466b2c734f8dde5d037b082cff1ccd9a913e7b87ecc73efa7d921ed1aa6ded"}, + {file = "lancedb-0.16.0-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:84980ccd4a170a5a07f83c85f90841e6995a05dc92cadedcb806401bc60f832b"}, + {file = "lancedb-0.16.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7135cd4733c3f7bcff7bc5f017bb79dda8e23dc3530476e0b91a4cb3ffef9e2e"}, + {file = "lancedb-0.16.0-cp38-abi3-win_amd64.whl", hash = "sha256:357a4cd2d7c5bf9677f84f7aa408d6c98c9d9012b3110979b126d40d30530e12"}, ] [package.dependencies] -attrs = ">=21.3.0" -cachetools = "*" deprecation = "*" +nest-asyncio = ">=1.0,<2.0" overrides = ">=0.7" packaging = "*" pydantic = ">=1.10" -pylance = "0.19.1" -requests = ">=2.31.0" +pylance = "0.19.2" tqdm = ">=4.27.0" [package.extras] @@ -3370,7 +3313,7 @@ azure = ["adlfs (>=2024.2.0)"] clip = ["open-clip", "pillow", "torch"] dev = ["pre-commit", "ruff"] docs = ["mkdocs", "mkdocs-jupyter", "mkdocs-material", "mkdocstrings[python]"] -embeddings = ["awscli (>=1.29.57)", "boto3 (>=1.28.57)", "botocore (>=1.31.57)", "cohere", "google-generativeai", "huggingface-hub", "ibm-watsonx-ai (>=1.1.2)", "instructorembedding", "ollama", "open-clip-torch", "openai (>=1.6.1)", "pillow", "sentence-transformers", "torch"] +embeddings = ["awscli (>=1.29.57)", "boto3 (>=1.28.57)", "botocore (>=1.31.57)", "cohere", "google-generativeai", "huggingface-hub", "ibm-watsonx-ai (>=1.1.2)", "instructorembedding", "ollama", "open-clip-torch", "openai (>=1.6.1)", "pillow", "requests (>=2.31.0)", "sentence-transformers", "torch"] tests = ["aiohttp", "boto3", "duckdb", "pandas (>=1.4)", "polars (>=0.19,<=1.3.0)", "pytest", "pytest-asyncio", "pytest-mock", "pytz", "tantivy"] [[package]] @@ -3390,7 +3333,10 @@ async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\ langchain-core = ">=0.3.24,<0.4.0" langchain-text-splitters = ">=0.3.0,<0.4.0" langsmith = ">=0.1.17,<0.3" -numpy = {version = ">=1.22.4,<2", markers = "python_version < \"3.12\""} +numpy = [ + {version = ">=1.22.4,<2", markers = "python_version < \"3.12\""}, + {version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""}, +] pydantic = ">=2.7.4,<3.0.0" PyYAML = ">=5.3" requests = ">=2,<3" @@ -3415,7 +3361,10 @@ httpx-sse = ">=0.4.0,<0.5.0" langchain = ">=0.3.11,<0.4.0" langchain-core = ">=0.3.24,<0.4.0" langsmith = ">=0.1.125,<0.3" -numpy = {version = ">=1.22.4,<2", markers = "python_version < \"3.12\""} +numpy = [ + {version = ">=1.22.4,<2", markers = "python_version < \"3.12\""}, + {version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""}, +] pydantic-settings = ">=2.4.0,<3.0.0" PyYAML = ">=5.3" requests = ">=2,<3" @@ -3424,38 +3373,41 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10" [[package]] name = "langchain-core" -version = "0.3.28" +version = "0.3.29" description = "Building applications with LLMs through composability" optional = true python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_core-0.3.28-py3-none-any.whl", hash = "sha256:a02f81ca53a8eed757133797e5a602ca80c1324bbecb0c5d86ef7bd3d6625372"}, - {file = "langchain_core-0.3.28.tar.gz", hash = "sha256:407f7607e6b3c0ebfd6094da95d39b701e22e59966698ef126799782953e7f2c"}, + {file = "langchain_core-0.3.29-py3-none-any.whl", hash = "sha256:817db1474871611a81105594a3e4d11704949661008e455a10e38ca9ff601a1a"}, + {file = "langchain_core-0.3.29.tar.gz", hash = "sha256:773d6aeeb612e7ce3d996c0be403433d8c6a91e77bbb7a7461c13e15cfbe5b06"}, ] [package.dependencies] jsonpatch = ">=1.33,<2.0" langsmith = ">=0.1.125,<0.3" packaging = ">=23.2,<25" -pydantic = {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""} +pydantic = [ + {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, +] PyYAML = ">=5.3" tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7" [[package]] name = "langchain-openai" -version = "0.2.5" +version = "0.2.14" description = "An integration package connecting OpenAI and LangChain" optional = true python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_openai-0.2.5-py3-none-any.whl", hash = "sha256:745fd9d51a5a3a9cb8839d41f3786ab38dfc539e47c713a806cbca32f3d0875c"}, - {file = "langchain_openai-0.2.5.tar.gz", hash = "sha256:55b98711a880474ec363267bf6cd0e2727dc00e8433731318d063a2184582c28"}, + {file = "langchain_openai-0.2.14-py3-none-any.whl", hash = "sha256:d232496662f79ece9a11caf7d798ba863e559c771bc366814f7688e0fe664fe8"}, + {file = "langchain_openai-0.2.14.tar.gz", hash = "sha256:7a514f309e356b182a337c0ed36ab3fbe34d9834a235a3b85cb7f91ae775d978"}, ] [package.dependencies] -langchain-core = ">=0.3.15,<0.4.0" -openai = ">=1.52.0,<2.0.0" +langchain-core = ">=0.3.27,<0.4.0" +openai = ">=1.58.1,<2.0.0" tiktoken = ">=0.7,<1" [[package]] @@ -3488,13 +3440,13 @@ six = "*" [[package]] name = "langfuse" -version = "2.57.0" +version = "2.57.5" description = "A client library for accessing langfuse" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langfuse-2.57.0-py3-none-any.whl", hash = "sha256:cb14b2393b73e5548ddd7e5cf2967210e24706e10b2699a727ece9077e69c990"}, - {file = "langfuse-2.57.0.tar.gz", hash = "sha256:8aa0b59af12c46417d1c9993f17921a984ec4688c0cd50004ef18647c9c5ae77"}, + {file = "langfuse-2.57.5-py3-none-any.whl", hash = "sha256:5bc53ee33e7e58b4d112abdf971f16bc9e7e8e8ba4690b3c4339d37b83601f80"}, + {file = "langfuse-2.57.5.tar.gz", hash = "sha256:48b4db5d4e86824153ad6f1e10d14c2c7994bcbf67578c2bdd6786bf3896c3e8"}, ] [package.dependencies] @@ -3526,7 +3478,10 @@ files = [ [package.dependencies] httpx = ">=0.23.0,<1" orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} -pydantic = {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""} +pydantic = [ + {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, +] requests = ">=2,<3" requests-toolbelt = ">=1.0.0,<2.0.0" @@ -3535,41 +3490,41 @@ langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] [[package]] name = "litellm" -version = "1.49.1" +version = "1.57.2" description = "Library to easily interface with LLM API providers" optional = false python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" files = [ - {file = "litellm-1.49.1-py3-none-any.whl", hash = "sha256:2ba6689fe4ea3b0d69f56f2843caff6422497489e6252943b13ef1463f016728"}, - {file = "litellm-1.49.1.tar.gz", hash = "sha256:f51450ad823c8bdf057017009ae8bcce1a2810690b2f0d9dcdaff04ddc68209a"}, + {file = "litellm-1.57.2-py3-none-any.whl", hash = "sha256:b572c0d3d3c33ff3a4d18928ac6f051d10ac159814017a817d88ec7af9a8600c"}, + {file = "litellm-1.57.2.tar.gz", hash = "sha256:0a07c4e288f4bd9033335d5606d7da497f1193d51cf262b96812f40b8842a842"}, ] [package.dependencies] aiohttp = "*" click = "*" +httpx = ">=0.23.0,<0.28.0" importlib-metadata = ">=6.8.0" jinja2 = ">=3.1.2,<4.0.0" jsonschema = ">=4.22.0,<5.0.0" -openai = ">=1.51.0" +openai = ">=1.55.3" pydantic = ">=2.0.0,<3.0.0" python-dotenv = ">=0.2.0" -requests = ">=2.31.0,<3.0.0" tiktoken = ">=0.7.0" tokenizers = "*" [package.extras] extra-proxy = ["azure-identity (>=1.15.0,<2.0.0)", "azure-keyvault-secrets (>=4.8.0,<5.0.0)", "google-cloud-kms (>=2.21.3,<3.0.0)", "prisma (==0.11.0)", "resend (>=0.8.0,<0.9.0)"] -proxy = ["PyJWT (>=2.8.0,<3.0.0)", "apscheduler (>=3.10.4,<4.0.0)", "backoff", "cryptography (>=42.0.5,<43.0.0)", "fastapi (>=0.111.0,<0.112.0)", "fastapi-sso (>=0.10.0,<0.11.0)", "gunicorn (>=22.0.0,<23.0.0)", "orjson (>=3.9.7,<4.0.0)", "pynacl (>=1.5.0,<2.0.0)", "python-multipart (>=0.0.9,<0.0.10)", "pyyaml (>=6.0.1,<7.0.0)", "rq", "uvicorn (>=0.22.0,<0.23.0)"] +proxy = ["PyJWT (>=2.8.0,<3.0.0)", "apscheduler (>=3.10.4,<4.0.0)", "backoff", "cryptography (>=43.0.1,<44.0.0)", "fastapi (>=0.115.5,<0.116.0)", "fastapi-sso (>=0.16.0,<0.17.0)", "gunicorn (>=22.0.0,<23.0.0)", "orjson (>=3.9.7,<4.0.0)", "pynacl (>=1.5.0,<2.0.0)", "python-multipart (>=0.0.18,<0.0.19)", "pyyaml (>=6.0.1,<7.0.0)", "rq", "uvicorn (>=0.22.0,<0.23.0)"] [[package]] name = "llama-index-core" -version = "0.11.23" +version = "0.12.10.post1" description = "Interface between LLMs and your data" optional = true -python-versions = "<4.0,>=3.8.1" +python-versions = "<4.0,>=3.9" files = [ - {file = "llama_index_core-0.11.23-py3-none-any.whl", hash = "sha256:25a0cb4a055bfb348655ca4acd1b475529bd8537a7b81874ef14ed13f56e06c1"}, - {file = "llama_index_core-0.11.23.tar.gz", hash = "sha256:e150859696a0eae169fe19323f46e9a31af2c12c3182012e4d0353ea8eb06d24"}, + {file = "llama_index_core-0.12.10.post1-py3-none-any.whl", hash = "sha256:897e8cd4efeff6842580b043bdf4008ac60f693df1de2bfd975307a4845707c2"}, + {file = "llama_index_core-0.12.10.post1.tar.gz", hash = "sha256:af27bea4d1494ba84983a649976e60e3de677a73946aa45ed12ce27e3a623ddf"}, ] [package.dependencies] @@ -3583,13 +3538,13 @@ httpx = "*" nest-asyncio = ">=1.5.8,<2.0.0" networkx = ">=3.0" nltk = ">3.8.1" -numpy = "<2.0.0" +numpy = "*" pillow = ">=9.0.0" -pydantic = ">=2.7.0,<3.0.0" +pydantic = ">=2.8.0" PyYAML = ">=6.0.1" requests = ">=2.31.0" SQLAlchemy = {version = ">=1.4.49", extras = ["asyncio"]} -tenacity = ">=8.2.0,<8.4.0 || >8.4.0,<9.0.0" +tenacity = ">=8.2.0,<8.4.0 || >8.4.0,<10.0.0" tiktoken = ">=0.3.3" tqdm = ">=4.66.1,<5.0.0" typing-extensions = ">=4.5.0" @@ -3791,9 +3746,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"] @@ -3894,13 +3846,13 @@ files = [ [[package]] name = "marshmallow" -version = "3.23.2" +version = "3.24.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = true python-versions = ">=3.9" files = [ - {file = "marshmallow-3.23.2-py3-none-any.whl", hash = "sha256:bcaf2d6fd74fb1459f8450e85d994997ad3e70036452cbfa4ab685acb19479b3"}, - {file = "marshmallow-3.23.2.tar.gz", hash = "sha256:c448ac6455ca4d794773f00bae22c2f351d62d739929f761dce5eacb5c468d7f"}, + {file = "marshmallow-3.24.1-py3-none-any.whl", hash = "sha256:ddb5c9987017d37be351c184e4e867e7bf55f7331f4da730dedad6b7af662cdd"}, + {file = "marshmallow-3.24.1.tar.gz", hash = "sha256:efdcb656ac8788f0e3d1d938f8dc0f237bf1a99aff8f6dfbffa594981641cea0"}, ] [package.dependencies] @@ -3908,64 +3860,56 @@ packaging = ">=17.0" [package.extras] dev = ["marshmallow[tests]", "pre-commit (>=3.5,<5.0)", "tox"] -docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.14)", "sphinx (==8.1.3)", "sphinx-issues (==5.0.0)", "sphinx-version-warning (==1.1.2)"] +docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.14)", "sphinx (==8.1.3)", "sphinx-issues (==5.0.0)"] tests = ["pytest", "simplejson"] [[package]] name = "matplotlib" -version = "3.9.4" +version = "3.10.0" description = "Python plotting package" optional = false -python-versions = ">=3.9" -files = [ - {file = "matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50"}, - {file = "matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff"}, - {file = "matplotlib-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddf9f3c26aae695c5daafbf6b94e4c1a30d6cd617ba594bbbded3b33a1fcfa26"}, - {file = "matplotlib-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18ebcf248030173b59a868fda1fe42397253f6698995b55e81e1f57431d85e50"}, - {file = "matplotlib-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974896ec43c672ec23f3f8c648981e8bc880ee163146e0312a9b8def2fac66f5"}, - {file = "matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d"}, - {file = "matplotlib-3.9.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4dd29641d9fb8bc4492420c5480398dd40a09afd73aebe4eb9d0071a05fbe0c"}, - {file = "matplotlib-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30e5b22e8bcfb95442bf7d48b0d7f3bdf4a450cbf68986ea45fca3d11ae9d099"}, - {file = "matplotlib-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bb0030d1d447fd56dcc23b4c64a26e44e898f0416276cac1ebc25522e0ac249"}, - {file = "matplotlib-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aca90ed222ac3565d2752b83dbb27627480d27662671e4d39da72e97f657a423"}, - {file = "matplotlib-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a181b2aa2906c608fcae72f977a4a2d76e385578939891b91c2550c39ecf361e"}, - {file = "matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3"}, - {file = "matplotlib-3.9.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dfc48d67e6661378a21c2983200a654b72b5c5cdbd5d2cf6e5e1ece860f0cc70"}, - {file = "matplotlib-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47aef0fab8332d02d68e786eba8113ffd6f862182ea2999379dec9e237b7e483"}, - {file = "matplotlib-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fba1f52c6b7dc764097f52fd9ab627b90db452c9feb653a59945de16752e965f"}, - {file = "matplotlib-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173ac3748acaac21afcc3fa1633924609ba1b87749006bc25051c52c422a5d00"}, - {file = "matplotlib-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320edea0cadc07007765e33f878b13b3738ffa9745c5f707705692df70ffe0e0"}, - {file = "matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b"}, - {file = "matplotlib-3.9.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37eeffeeca3c940985b80f5b9a7b95ea35671e0e7405001f249848d2b62351b6"}, - {file = "matplotlib-3.9.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3e7465ac859ee4abcb0d836137cd8414e7bb7ad330d905abced457217d4f0f45"}, - {file = "matplotlib-3.9.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c12302c34afa0cf061bea23b331e747e5e554b0fa595c96e01c7b75bc3b858"}, - {file = "matplotlib-3.9.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8c97917f21b75e72108b97707ba3d48f171541a74aa2a56df7a40626bafc64"}, - {file = "matplotlib-3.9.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0229803bd7e19271b03cb09f27db76c918c467aa4ce2ae168171bc67c3f508df"}, - {file = "matplotlib-3.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:7c0d8ef442ebf56ff5e206f8083d08252ee738e04f3dc88ea882853a05488799"}, - {file = "matplotlib-3.9.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a04c3b00066a688834356d196136349cb32f5e1003c55ac419e91585168b88fb"}, - {file = "matplotlib-3.9.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04c519587f6c210626741a1e9a68eefc05966ede24205db8982841826af5871a"}, - {file = "matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308afbf1a228b8b525fcd5cec17f246bbbb63b175a3ef6eb7b4d33287ca0cf0c"}, - {file = "matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb3b02246ddcffd3ce98e88fed5b238bc5faff10dbbaa42090ea13241d15764"}, - {file = "matplotlib-3.9.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8a75287e9cb9eee48cb79ec1d806f75b29c0fde978cb7223a1f4c5848d696041"}, - {file = "matplotlib-3.9.4-cp313-cp313t-win_amd64.whl", hash = "sha256:488deb7af140f0ba86da003e66e10d55ff915e152c78b4b66d231638400b1965"}, - {file = "matplotlib-3.9.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3c3724d89a387ddf78ff88d2a30ca78ac2b4c89cf37f2db4bd453c34799e933c"}, - {file = "matplotlib-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5f0a8430ffe23d7e32cfd86445864ccad141797f7d25b7c41759a5b5d17cfd7"}, - {file = "matplotlib-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb0141a21aef3b64b633dc4d16cbd5fc538b727e4958be82a0e1c92a234160e"}, - {file = "matplotlib-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57aa235109e9eed52e2c2949db17da185383fa71083c00c6c143a60e07e0888c"}, - {file = "matplotlib-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b18c600061477ccfdd1e6fd050c33d8be82431700f3452b297a56d9ed7037abb"}, - {file = "matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac"}, - {file = "matplotlib-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:44e0ed786d769d85bc787b0606a53f2d8d2d1d3c8a2608237365e9121c1a338c"}, - {file = "matplotlib-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:09debb9ce941eb23ecdbe7eab972b1c3e0276dcf01688073faff7b0f61d6c6ca"}, - {file = "matplotlib-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc53cf157a657bfd03afab14774d54ba73aa84d42cfe2480c91bd94873952db"}, - {file = "matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865"}, - {file = "matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3"}, +python-versions = ">=3.10" +files = [ + {file = "matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6"}, + {file = "matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e"}, + {file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5"}, + {file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6"}, + {file = "matplotlib-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1"}, + {file = "matplotlib-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3"}, + {file = "matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363"}, + {file = "matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997"}, + {file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef"}, + {file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683"}, + {file = "matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765"}, + {file = "matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a"}, + {file = "matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59"}, + {file = "matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a"}, + {file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95"}, + {file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8"}, + {file = "matplotlib-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12"}, + {file = "matplotlib-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc"}, + {file = "matplotlib-3.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96f2886f5c1e466f21cc41b70c5a0cd47bfa0015eb2d5793c88ebce658600e25"}, + {file = "matplotlib-3.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:12eaf48463b472c3c0f8dbacdbf906e573013df81a0ab82f0616ea4b11281908"}, + {file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fbbabc82fde51391c4da5006f965e36d86d95f6ee83fb594b279564a4c5d0d2"}, + {file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad2e15300530c1a94c63cfa546e3b7864bd18ea2901317bae8bbf06a5ade6dcf"}, + {file = "matplotlib-3.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3547d153d70233a8496859097ef0312212e2689cdf8d7ed764441c77604095ae"}, + {file = "matplotlib-3.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c55b20591ced744aa04e8c3e4b7543ea4d650b6c3c4b208c08a05b4010e8b442"}, + {file = "matplotlib-3.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ade1003376731a971e398cc4ef38bb83ee8caf0aee46ac6daa4b0506db1fd06"}, + {file = "matplotlib-3.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95b710fea129c76d30be72c3b38f330269363fbc6e570a5dd43580487380b5ff"}, + {file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdbaf909887373c3e094b0318d7ff230b2ad9dcb64da7ade654182872ab2593"}, + {file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d907fddb39f923d011875452ff1eca29a9e7f21722b873e90db32e5d8ddff12e"}, + {file = "matplotlib-3.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3b427392354d10975c1d0f4ee18aa5844640b512d5311ef32efd4dd7db106ede"}, + {file = "matplotlib-3.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5fd41b0ec7ee45cd960a8e71aea7c946a28a0b8a4dcee47d2856b2af051f334c"}, + {file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03"}, + {file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea"}, + {file = "matplotlib-3.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef"}, + {file = "matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278"}, ] [package.dependencies] 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" @@ -3974,7 +3918,7 @@ pyparsing = ">=2.3.1" python-dateutil = ">=2.7" [package.extras] -dev = ["meson-python (>=0.13.1,<0.17.0)", "numpy (>=1.25)", "pybind11 (>=2.6,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] [[package]] name = "matplotlib-inline" @@ -4025,15 +3969,15 @@ files = [ [[package]] name = "milvus-lite" -version = "2.4.10" +version = "2.4.11" description = "A lightweight version of Milvus wrapped with Python." optional = true python-versions = ">=3.7" files = [ - {file = "milvus_lite-2.4.10-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:fc4246d3ed7d1910847afce0c9ba18212e93a6e9b8406048436940578dfad5cb"}, - {file = "milvus_lite-2.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:74a8e07c5e3b057df17fbb46913388e84df1dc403a200f4e423799a58184c800"}, - {file = "milvus_lite-2.4.10-py3-none-manylinux2014_aarch64.whl", hash = "sha256:240c7386b747bad696ecb5bd1f58d491e86b9d4b92dccee3315ed7256256eddc"}, - {file = "milvus_lite-2.4.10-py3-none-manylinux2014_x86_64.whl", hash = "sha256:211d2e334a043f9282bdd9755f76b9b2d93b23bffa7af240919ffce6a8dfe325"}, + {file = "milvus_lite-2.4.11-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9e563ae0dca1b41bfd76b90f06b2bcc474460fe4eba142c9bab18d2747ff843b"}, + {file = "milvus_lite-2.4.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d21472bd24eb327542817829ce7cb51878318e6173c4d62353c77421aecf98d6"}, + {file = "milvus_lite-2.4.11-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8e6ef27f7f84976f9fd0047b675ede746db2e0cc581c44a916ac9e71e0cef05d"}, + {file = "milvus_lite-2.4.11-py3-none-manylinux2014_x86_64.whl", hash = "sha256:551f56b49fcfbb330b658b4a3c56ed29ba9b692ec201edd1f2dade7f5e39957d"}, ] [package.dependencies] @@ -4041,15 +3985,18 @@ tqdm = "*" [[package]] name = "mistune" -version = "3.0.2" +version = "3.1.0" description = "A sane and fast Markdown parser with useful plugins and renderers" optional = true -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"}, - {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, + {file = "mistune-3.1.0-py3-none-any.whl", hash = "sha256:b05198cf6d671b3deba6c87ec6cf0d4eb7b72c524636eddb6dbf13823b52cee1"}, + {file = "mistune-3.1.0.tar.gz", hash = "sha256:dbcac2f78292b9dc066cd03b7a3a26b62d85f8159f2ea5fd28e55df79908d667"}, ] +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.11\""} + [[package]] name = "mkdocs" version = "1.6.1" @@ -4065,7 +4012,6 @@ files = [ click = ">=7.0" colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" markdown = ">=3.3.6" markupsafe = ">=2.0.1" @@ -4109,7 +4055,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} mergedeep = ">=1.3.4" platformdirs = ">=2.2.0" pyyaml = ">=5.1" @@ -4184,7 +4129,6 @@ files = [ [package.dependencies] click = ">=7.0" -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} Jinja2 = ">=2.11.1" Markdown = ">=3.6" MarkupSafe = ">=1.1" @@ -4193,7 +4137,6 @@ mkdocs-autorefs = ">=1.2" mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} platformdirs = ">=2.2" pymdown-extensions = ">=6.3" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} [package.extras] crystal = ["mkdocstrings-crystal (>=0.3.4)"] @@ -4202,13 +4145,13 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.12.2" +version = "1.13.0" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.9" files = [ - {file = "mkdocstrings_python-1.12.2-py3-none-any.whl", hash = "sha256:7f7d40d6db3cb1f5d19dbcd80e3efe4d0ba32b073272c0c0de9de2e604eda62a"}, - {file = "mkdocstrings_python-1.12.2.tar.gz", hash = "sha256:7a1760941c0b52a2cd87b960a9e21112ffe52e7df9d0b9583d04d47ed2e186f3"}, + {file = "mkdocstrings_python-1.13.0-py3-none-any.whl", hash = "sha256:b88bbb207bab4086434743849f8e796788b373bd32e7bfefbf8560ac45d88f97"}, + {file = "mkdocstrings_python-1.13.0.tar.gz", hash = "sha256:2dbd5757e8375b9720e81db16f52f1856bf59905428fd7ef88005d1370e2f64c"}, ] [package.dependencies] @@ -4357,49 +4300,55 @@ dill = ">=0.3.8" [[package]] name = "mypy" -version = "1.13.0" +version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" + {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, + {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, + {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, + {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, + {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, + {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, + {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, + {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, + {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, + {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, + {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, + {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, + {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, + {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, + {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -4443,20 +4392,19 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= [[package]] name = "nbconvert" -version = "7.16.4" +version = "7.16.5" description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." optional = true python-versions = ">=3.8" files = [ - {file = "nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3"}, - {file = "nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4"}, + {file = "nbconvert-7.16.5-py3-none-any.whl", hash = "sha256:e12eac052d6fd03040af4166c563d76e7aeead2e9aadf5356db552a1784bd547"}, + {file = "nbconvert-7.16.5.tar.gz", hash = "sha256:c83467bb5777fdfaac5ebbb8e864f300b277f68692ecc04d6dab72f2d8442344"}, ] [package.dependencies] beautifulsoup4 = "*" -bleach = "!=5.0.0" +bleach = {version = "!=5.0.0", extras = ["css"]} defusedxml = "*" -importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} jinja2 = ">=3.0" jupyter-core = ">=4.7" jupyterlab-pygments = "*" @@ -4467,7 +4415,6 @@ nbformat = ">=5.7" packaging = "*" pandocfilters = ">=1.4.1" pygments = ">=2.4.1" -tinycss2 = "*" traitlets = ">=5.1" [package.extras] @@ -4500,6 +4447,17 @@ traitlets = ">=5.1" docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] test = ["pep440", "pre-commit", "pytest", "testpath"] +[[package]] +name = "ndjson" +version = "0.3.1" +description = "JsonDecoder for ndjson" +optional = true +python-versions = "*" +files = [ + {file = "ndjson-0.3.1-py2.py3-none-any.whl", hash = "sha256:839c22275e6baa3040077b83c005ac24199b94973309a8a1809be962c753a410"}, + {file = "ndjson-0.3.1.tar.gz", hash = "sha256:bf9746cb6bb1cb53d172cda7f154c07c786d665ff28341e4e689b796b229e5d6"}, +] + [[package]] name = "neo4j" version = "5.27.0" @@ -4532,20 +4490,21 @@ files = [ [[package]] name = "networkx" -version = "3.2.1" +version = "3.4.2" description = "Python package for creating and manipulating graphs and networks" optional = false -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.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, + {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, ] [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.7)", "numpy (>=1.24)", "pandas (>=2.0)", "scipy (>=1.10,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.15)", "sphinx (>=7.3)", "sphinx-gallery (>=0.16)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=1.9)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] @@ -4586,18 +4545,18 @@ files = [ [[package]] name = "notebook" -version = "7.3.1" +version = "7.3.2" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" optional = true python-versions = ">=3.8" files = [ - {file = "notebook-7.3.1-py3-none-any.whl", hash = "sha256:212e1486b2230fe22279043f33c7db5cf9a01d29feb063a85cb139747b7c9483"}, - {file = "notebook-7.3.1.tar.gz", hash = "sha256:84381c2a82d867517fd25b86e986dae1fe113a70b98f03edff9b94e499fec8fa"}, + {file = "notebook-7.3.2-py3-none-any.whl", hash = "sha256:e5f85fc59b69d3618d73cf27544418193ff8e8058d5bf61d315ce4f473556288"}, + {file = "notebook-7.3.2.tar.gz", hash = "sha256:705e83a1785f45b383bf3ee13cb76680b92d24f56fb0c7d2136fe1d850cd3ca8"}, ] [package.dependencies] jupyter-server = ">=2.4.0,<3" -jupyterlab = ">=4.3.2,<4.4" +jupyterlab = ">=4.3.4,<4.4" jupyterlab-server = ">=2.27.1,<3" notebook-shim = ">=0.2,<0.3" tornado = ">=6.2.0" @@ -4701,13 +4660,13 @@ tests = ["pytest", "pytest-cov"] [[package]] name = "openai" -version = "1.52.0" +version = "1.59.4" description = "The official Python library for the openai API" optional = false -python-versions = ">=3.7.1" +python-versions = ">=3.8" files = [ - {file = "openai-1.52.0-py3-none-any.whl", hash = "sha256:0c249f20920183b0a2ca4f7dba7b0452df3ecd0fa7985eb1d91ad884bc3ced9c"}, - {file = "openai-1.52.0.tar.gz", hash = "sha256:95c65a5f77559641ab8f3e4c3a050804f7b51d278870e2ec1f7444080bfe565a"}, + {file = "openai-1.59.4-py3-none-any.whl", hash = "sha256:82113498699998e98104f87c19a890e82df9b01251a0395484360575d3a1d98a"}, + {file = "openai-1.59.4.tar.gz", hash = "sha256:b946dc5a2308dc1e03efbda80bf1cd64b6053b536851ad519f57ee44401663d2"}, ] [package.dependencies] @@ -4722,6 +4681,7 @@ typing-extensions = ">=4.11,<5" [package.extras] datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] +realtime = ["websockets (>=13,<15)"] [[package]] name = "openpyxl" @@ -4844,86 +4804,86 @@ files = [ [[package]] name = "orjson" -version = "3.10.12" +version = "3.10.13" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"}, - {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"}, - {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"}, - {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"}, - {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"}, - {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"}, - {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"}, - {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"}, - {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"}, - {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"}, - {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"}, - {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"}, - {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"}, - {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"}, - {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"}, - {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"}, - {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"}, - {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"}, - {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"}, + {file = "orjson-3.10.13-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1232c5e873a4d1638ef957c5564b4b0d6f2a6ab9e207a9b3de9de05a09d1d920"}, + {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26a0eca3035619fa366cbaf49af704c7cb1d4a0e6c79eced9f6a3f2437964b6"}, + {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d4b6acd7c9c829895e50d385a357d4b8c3fafc19c5989da2bae11783b0fd4977"}, + {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1884e53c6818686891cc6fc5a3a2540f2f35e8c76eac8dc3b40480fb59660b00"}, + {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a428afb5720f12892f64920acd2eeb4d996595bf168a26dd9190115dbf1130d"}, + {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba5b13b8739ce5b630c65cb1c85aedbd257bcc2b9c256b06ab2605209af75a2e"}, + {file = "orjson-3.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cab83e67f6aabda1b45882254b2598b48b80ecc112968fc6483fa6dae609e9f0"}, + {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62c3cc00c7e776c71c6b7b9c48c5d2701d4c04e7d1d7cdee3572998ee6dc57cc"}, + {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:dc03db4922e75bbc870b03fc49734cefbd50fe975e0878327d200022210b82d8"}, + {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:22f1c9a30b43d14a041a6ea190d9eca8a6b80c4beb0e8b67602c82d30d6eec3e"}, + {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b42f56821c29e697c68d7d421410d7c1d8f064ae288b525af6a50cf99a4b1200"}, + {file = "orjson-3.10.13-cp310-cp310-win32.whl", hash = "sha256:0dbf3b97e52e093d7c3e93eb5eb5b31dc7535b33c2ad56872c83f0160f943487"}, + {file = "orjson-3.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:46c249b4e934453be4ff2e518cd1adcd90467da7391c7a79eaf2fbb79c51e8c7"}, + {file = "orjson-3.10.13-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a36c0d48d2f084c800763473020a12976996f1109e2fcb66cfea442fdf88047f"}, + {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0065896f85d9497990731dfd4a9991a45b0a524baec42ef0a63c34630ee26fd6"}, + {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92b4ec30d6025a9dcdfe0df77063cbce238c08d0404471ed7a79f309364a3d19"}, + {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a94542d12271c30044dadad1125ee060e7a2048b6c7034e432e116077e1d13d2"}, + {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3723e137772639af8adb68230f2aa4bcb27c48b3335b1b1e2d49328fed5e244c"}, + {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f00c7fb18843bad2ac42dc1ce6dd214a083c53f1e324a0fd1c8137c6436269b"}, + {file = "orjson-3.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e2759d3172300b2f892dee85500b22fca5ac49e0c42cfff101aaf9c12ac9617"}, + {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee948c6c01f6b337589c88f8e0bb11e78d32a15848b8b53d3f3b6fea48842c12"}, + {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa6fe68f0981fba0d4bf9cdc666d297a7cdba0f1b380dcd075a9a3dd5649a69e"}, + {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbcd7aad6bcff258f6896abfbc177d54d9b18149c4c561114f47ebfe74ae6bfd"}, + {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2149e2fcd084c3fd584881c7f9d7f9e5ad1e2e006609d8b80649655e0d52cd02"}, + {file = "orjson-3.10.13-cp311-cp311-win32.whl", hash = "sha256:89367767ed27b33c25c026696507c76e3d01958406f51d3a2239fe9e91959df2"}, + {file = "orjson-3.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:dca1d20f1af0daff511f6e26a27354a424f0b5cf00e04280279316df0f604a6f"}, + {file = "orjson-3.10.13-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a3614b00621c77f3f6487792238f9ed1dd8a42f2ec0e6540ee34c2d4e6db813a"}, + {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c976bad3996aa027cd3aef78aa57873f3c959b6c38719de9724b71bdc7bd14b"}, + {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f74d878d1efb97a930b8a9f9898890067707d683eb5c7e20730030ecb3fb930"}, + {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33ef84f7e9513fb13b3999c2a64b9ca9c8143f3da9722fbf9c9ce51ce0d8076e"}, + {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd2bcde107221bb9c2fa0c4aaba735a537225104173d7e19cf73f70b3126c993"}, + {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:064b9dbb0217fd64a8d016a8929f2fae6f3312d55ab3036b00b1d17399ab2f3e"}, + {file = "orjson-3.10.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0044b0b8c85a565e7c3ce0a72acc5d35cda60793edf871ed94711e712cb637d"}, + {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7184f608ad563032e398f311910bc536e62b9fbdca2041be889afcbc39500de8"}, + {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d36f689e7e1b9b6fb39dbdebc16a6f07cbe994d3644fb1c22953020fc575935f"}, + {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54433e421618cd5873e51c0e9d0b9fb35f7bf76eb31c8eab20b3595bb713cd3d"}, + {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e1ba0c5857dd743438acecc1cd0e1adf83f0a81fee558e32b2b36f89e40cee8b"}, + {file = "orjson-3.10.13-cp312-cp312-win32.whl", hash = "sha256:a42b9fe4b0114b51eb5cdf9887d8c94447bc59df6dbb9c5884434eab947888d8"}, + {file = "orjson-3.10.13-cp312-cp312-win_amd64.whl", hash = "sha256:3a7df63076435f39ec024bdfeb4c9767ebe7b49abc4949068d61cf4857fa6d6c"}, + {file = "orjson-3.10.13-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2cdaf8b028a976ebab837a2c27b82810f7fc76ed9fb243755ba650cc83d07730"}, + {file = "orjson-3.10.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a946796e390cbb803e069472de37f192b7a80f4ac82e16d6eb9909d9e39d56"}, + {file = "orjson-3.10.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d64f1db5ecbc21eb83097e5236d6ab7e86092c1cd4c216c02533332951afc"}, + {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:711878da48f89df194edd2ba603ad42e7afed74abcd2bac164685e7ec15f96de"}, + {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cf16f06cb77ce8baf844bc222dbcb03838f61d0abda2c3341400c2b7604e436e"}, + {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8257c3fb8dd7b0b446b5e87bf85a28e4071ac50f8c04b6ce2d38cb4abd7dff57"}, + {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9c3a87abe6f849a4a7ac8a8a1dede6320a4303d5304006b90da7a3cd2b70d2c"}, + {file = "orjson-3.10.13-cp313-cp313-win32.whl", hash = "sha256:527afb6ddb0fa3fe02f5d9fba4920d9d95da58917826a9be93e0242da8abe94a"}, + {file = "orjson-3.10.13-cp313-cp313-win_amd64.whl", hash = "sha256:b5f7c298d4b935b222f52d6c7f2ba5eafb59d690d9a3840b7b5c5cda97f6ec5c"}, + {file = "orjson-3.10.13-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e49333d1038bc03a25fdfe11c86360df9b890354bfe04215f1f54d030f33c342"}, + {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:003721c72930dbb973f25c5d8e68d0f023d6ed138b14830cc94e57c6805a2eab"}, + {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63664bf12addb318dc8f032160e0f5dc17eb8471c93601e8f5e0d07f95003784"}, + {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6066729cf9552d70de297b56556d14b4f49c8f638803ee3c90fd212fa43cc6af"}, + {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a1152e2761025c5d13b5e1908d4b1c57f3797ba662e485ae6f26e4e0c466388"}, + {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b21d91c5c5ef8a201036d207b1adf3aa596b930b6ca3c71484dd11386cf6c3"}, + {file = "orjson-3.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b12a63f48bb53dba8453d36ca2661f2330126d54e26c1661e550b32864b28ce3"}, + {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a5a7624ab4d121c7e035708c8dd1f99c15ff155b69a1c0affc4d9d8b551281ba"}, + {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0fee076134398d4e6cb827002468679ad402b22269510cf228301b787fdff5ae"}, + {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ae537fcf330b3947e82c6ae4271e092e6cf16b9bc2cef68b14ffd0df1fa8832a"}, + {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f81b26c03f5fb5f0d0ee48d83cea4d7bc5e67e420d209cc1a990f5d1c62f9be0"}, + {file = "orjson-3.10.13-cp38-cp38-win32.whl", hash = "sha256:0bc858086088b39dc622bc8219e73d3f246fb2bce70a6104abd04b3a080a66a8"}, + {file = "orjson-3.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:3ca6f17467ebbd763f8862f1d89384a5051b461bb0e41074f583a0ebd7120e8e"}, + {file = "orjson-3.10.13-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4a11532cbfc2f5752c37e84863ef8435b68b0e6d459b329933294f65fa4bda1a"}, + {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c96d2fb80467d1d0dfc4d037b4e1c0f84f1fe6229aa7fea3f070083acef7f3d7"}, + {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dda4ba4d3e6f6c53b6b9c35266788053b61656a716a7fef5c884629c2a52e7aa"}, + {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f998bbf300690be881772ee9c5281eb9c0044e295bcd4722504f5b5c6092ff"}, + {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1cc42ed75b585c0c4dc5eb53a90a34ccb493c09a10750d1a1f9b9eff2bd12"}, + {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03b0f29d485411e3c13d79604b740b14e4e5fb58811743f6f4f9693ee6480a8f"}, + {file = "orjson-3.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:233aae4474078d82f425134bb6a10fb2b3fc5a1a1b3420c6463ddd1b6a97eda8"}, + {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e384e330a67cf52b3597ee2646de63407da6f8fc9e9beec3eaaaef5514c7a1c9"}, + {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4222881d0aab76224d7b003a8e5fdae4082e32c86768e0e8652de8afd6c4e2c1"}, + {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e400436950ba42110a20c50c80dff4946c8e3ec09abc1c9cf5473467e83fd1c5"}, + {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f47c9e7d224b86ffb086059cdcf634f4b3f32480f9838864aa09022fe2617ce2"}, + {file = "orjson-3.10.13-cp39-cp39-win32.whl", hash = "sha256:a9ecea472f3eb653e1c0a3d68085f031f18fc501ea392b98dcca3e87c24f9ebe"}, + {file = "orjson-3.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:5385935a73adce85cc7faac9d396683fd813566d3857fa95a0b521ef84a5b588"}, + {file = "orjson-3.10.13.tar.gz", hash = "sha256:eb9bfb14ab8f68d9d9492d4817ae497788a15fd7da72e14dfabc289c3bb088ec"}, ] [[package]] @@ -4976,70 +4936,89 @@ files = [ [[package]] name = "pandas" -version = "2.0.3" +version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, - {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, - {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, - {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, - {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, - {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, - {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, - {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, - {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, - {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, - {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, ] [package.dependencies] numpy = [ - {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {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" -tzdata = ">=2022.1" - -[package.extras] -all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] -aws = ["s3fs (>=2021.08.0)"] -clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] -compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] -computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] -feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2021.07.0)"] -gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] -hdf5 = ["tables (>=3.6.1)"] -html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] -mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] -parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] -plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] -spss = ["pyreadstat (>=1.1.2)"] -sql-other = ["SQLAlchemy (>=1.4.16)"] -test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.6.3)"] +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] [[package]] name = "pandocfilters" @@ -5080,18 +5059,18 @@ files = [ [[package]] name = "pathvalidate" -version = "3.2.1" +version = "3.2.3" description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pathvalidate-3.2.1-py3-none-any.whl", hash = "sha256:9a6255eb8f63c9e2135b9be97a5ce08f10230128c4ae7b3e935378b82b22c4c9"}, - {file = "pathvalidate-3.2.1.tar.gz", hash = "sha256:f5d07b1e2374187040612a1fcd2bcb2919f8db180df254c9581bb90bf903377d"}, + {file = "pathvalidate-3.2.3-py3-none-any.whl", hash = "sha256:5eaf0562e345d4b6d0c0239d0f690c3bd84d2a9a3c4c73b99ea667401b27bee1"}, + {file = "pathvalidate-3.2.3.tar.gz", hash = "sha256:59b5b9278e30382d6d213497623043ebe63f10e29055be4419a9c04c721739cb"}, ] [package.extras] -docs = ["Sphinx (>=2.4)", "sphinx-rtd-theme (>=1.2.2)", "urllib3 (<2)"] -readme = ["path (>=13,<17)", "readmemaker (>=1.1.0)"] +docs = ["Sphinx (>=2.4)", "sphinx_rtd_theme (>=1.2.2)", "urllib3 (<2)"] +readme = ["path (>=13,<18)", "readmemaker (>=1.2.0)"] test = ["Faker (>=1.0.8)", "allpairspy (>=2)", "click (>=6.2)", "pytest (>=6.0.1)", "pytest-md-report (>=0.6.2)"] [[package]] @@ -5223,93 +5202,89 @@ numpy = "*" [[package]] name = "pillow" -version = "11.0.0" +version = "11.1.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" files = [ - {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, - {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, - {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, - {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, - {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, - {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, - {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, - {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, - {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, - {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, - {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, - {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, - {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, - {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, - {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, - {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, - {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, - {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, - {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, - {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, - {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, - {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, - {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, - {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, - {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, - {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, - {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, - {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, - {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, - {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, - {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, - {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, - {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, - {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, - {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, - {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, - {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, - {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, - {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, - {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, - {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, - {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, - {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, - {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"}, + {file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"}, + {file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"}, + {file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"}, + {file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"}, + {file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"}, + {file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"}, + {file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"}, + {file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"}, + {file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"}, + {file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"}, + {file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"}, + {file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"}, + {file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"}, + {file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"}, + {file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"}, + {file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"}, + {file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"}, + {file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"}, + {file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"}, + {file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] typing = ["typing-extensions"] xmp = ["defusedxml"] @@ -5376,13 +5351,13 @@ tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "p [[package]] name = "posthog" -version = "3.7.4" +version = "3.7.5" description = "Integrate PostHog into any python application." optional = true python-versions = "*" files = [ - {file = "posthog-3.7.4-py2.py3-none-any.whl", hash = "sha256:21c18c6bf43b2de303ea4cd6e95804cc0f24c20cb2a96a8fd09da2ed50b62faa"}, - {file = "posthog-3.7.4.tar.gz", hash = "sha256:19384bd09d330f9787a7e2446aba14c8057ece56144970ea2791072d4e40cd36"}, + {file = "posthog-3.7.5-py2.py3-none-any.whl", hash = "sha256:022132c17069dde03c5c5904e2ae1b9bd68d5059cbc5a8dffc5c1537a1b71cb5"}, + {file = "posthog-3.7.5.tar.gz", hash = "sha256:8ba40ab623da35db72715fc87fe7dccb7fc272ced92581fe31db2d4dbe7ad761"}, ] [package.dependencies] @@ -5833,13 +5808,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.7.0" +version = "2.7.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.7.0-py3-none-any.whl", hash = "sha256:e00c05d5fa6cbbb227c84bd7487c5c1065084119b750df7c8c1a554aed236eb5"}, - {file = "pydantic_settings-2.7.0.tar.gz", hash = "sha256:ac4bfd4a36831a48dbf8b2d9325425b549a0a6f18cea118436d728eb4f1c4d66"}, + {file = "pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd"}, + {file = "pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93"}, ] [package.dependencies] @@ -5853,13 +5828,13 @@ yaml = ["pyyaml (>=6.0.1)"] [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -5867,13 +5842,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "2.9.0" +version = "2.10.1" description = "JSON Web Token implementation in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, - {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, ] [package.dependencies] @@ -5887,21 +5862,21 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylance" -version = "0.19.1" +version = "0.19.2" description = "python wrapper for Lance columnar format" optional = false python-versions = ">=3.9" files = [ - {file = "pylance-0.19.1-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:a254d09690a5e09cadc5fecc7b43b2bfc20b477e0f0ba31497e1d6abb36b524a"}, - {file = "pylance-0.19.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:9859c372b2d7fe443b6218f62e9d77caf94961cac73b274c85b724f20dd6b690"}, - {file = "pylance-0.19.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8315152f57329e7668ff5c82c252591ea0e3d2aed702dd19a42d645945e7a07e"}, - {file = "pylance-0.19.1-cp39-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:7c2e0e00b40214edae576075dbfa432cadaf5ba21354b0c46f307daf4e77403f"}, - {file = "pylance-0.19.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e26ce273840912c45dd2b8f6f8fb9082c1c788d696e11b78ddad3949e3892d50"}, - {file = "pylance-0.19.1-cp39-abi3-win_amd64.whl", hash = "sha256:b341e547c995b5d6b32eb63e1e015d31b608de49a9ad03f8981453f4c667e8e1"}, + {file = "pylance-0.19.2-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:659b2ba45b7c905a2873bb36e9b4a6ec4634690723d45af0b469a502acacf5eb"}, + {file = "pylance-0.19.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:6a15b8b09e015feb11307ff63ef0742f9e120100e17476b1091d3db543c19bdf"}, + {file = "pylance-0.19.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf1732ff03acceacc6793f6b209357a757ce3cfd5a94369a81b3d15e8e425f9a"}, + {file = "pylance-0.19.2-cp39-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:c3be7883ae860c39186f41798cd752b93298450cc09488108f2aa738aa352f0e"}, + {file = "pylance-0.19.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:48a618dfc493932b49a8c1f50ad031e39c4d505d30c968d6467da1a03720a523"}, + {file = "pylance-0.19.2-cp39-abi3-win_amd64.whl", hash = "sha256:70d47d94521fc973460c8d765c3960db79a1f676aab658434693ab3e5a7112c1"}, ] [package.dependencies] -numpy = ">=1.22,<2" +numpy = ">=1.22" pyarrow = ">=12" [package.extras] @@ -5909,34 +5884,34 @@ benchmarks = ["pytest-benchmark"] cuvs-cu11 = ["cuvs-cu11", "pylibraft-cu11"] cuvs-cu12 = ["cuvs-cu12", "pylibraft-cu12"] dev = ["ruff (==0.4.1)"] -ray = ["ray[data]"] +ray = ["ray[data] (<2.38)"] tests = ["boto3", "datasets", "duckdb", "ml-dtypes", "pandas", "pillow", "polars[pandas,pyarrow]", "pytest", "tensorflow", "tqdm"] torch = ["torch"] [[package]] name = "pylint" -version = "3.3.2" +version = "3.3.3" description = "python code static checker" optional = false python-versions = ">=3.9.0" files = [ - {file = "pylint-3.3.2-py3-none-any.whl", hash = "sha256:77f068c287d49b8683cd7c6e624243c74f92890f767f106ffa1ddf3c0a54cb7a"}, - {file = "pylint-3.3.2.tar.gz", hash = "sha256:9ec054ec992cd05ad30a6df1676229739a73f8feeabf3912c995d17601052b01"}, + {file = "pylint-3.3.3-py3-none-any.whl", hash = "sha256:26e271a2bc8bce0fc23833805a9076dd9b4d5194e2a02164942cb3cdc37b4183"}, + {file = "pylint-3.3.3.tar.gz", hash = "sha256:07c607523b17e6d16e2ae0d7ef59602e332caa762af64203c24b41c27139f36a"}, ] [package.dependencies] -astroid = ">=3.3.5,<=3.4.0-dev0" +astroid = ">=3.3.8,<=3.4.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] spelling = ["pyenchant (>=3.2,<4.0)"] @@ -5944,13 +5919,13 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymdown-extensions" -version = "10.12" +version = "10.14" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.12-py3-none-any.whl", hash = "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77"}, - {file = "pymdown_extensions-10.12.tar.gz", hash = "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7"}, + {file = "pymdown_extensions-10.14-py3-none-any.whl", hash = "sha256:202481f716cc8250e4be8fce997781ebf7917701b59652458ee47f2401f818b5"}, + {file = "pymdown_extensions-10.14.tar.gz", hash = "sha256:741bd7c4ff961ba40b7528d32284c53bc436b8b1645e8e37c3e57770b8700a34"}, ] [package.dependencies] @@ -5958,17 +5933,17 @@ markdown = ">=3.6" pyyaml = "*" [package.extras] -extra = ["pygments (>=2.12)"] +extra = ["pygments (>=2.19.1)"] [[package]] name = "pymilvus" -version = "2.5.0" +version = "2.5.3" description = "Python Sdk for Milvus" optional = true python-versions = ">=3.8" files = [ - {file = "pymilvus-2.5.0-py3-none-any.whl", hash = "sha256:a0e8653d8fe78019abfda79b3404ef7423f312501e8cbd7dc728051ce8732652"}, - {file = "pymilvus-2.5.0.tar.gz", hash = "sha256:4da14a3bd957a4921166f9355fd1f1ac5c5e4e80b46f12f64d9c9a6dcb8cb395"}, + {file = "pymilvus-2.5.3-py3-none-any.whl", hash = "sha256:64ca63594284586937274800be27a402f3be2d078130bf81d94ab8d7798ac9c8"}, + {file = "pymilvus-2.5.3.tar.gz", hash = "sha256:68bc3797b7a14c494caf116cee888894ffd6eba7b96a3ac841be85d60694cc5d"}, ] [package.dependencies] @@ -5998,13 +5973,13 @@ files = [ [[package]] name = "pyparsing" -version = "3.2.0" +version = "3.2.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" files = [ - {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, - {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, + {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, + {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, ] [package.extras] @@ -6173,9 +6148,6 @@ files = [ {file = "python_json_logger-3.2.1.tar.gz", hash = "sha256:8eb0554ea17cb75b05d2848bc14fb02fbdbd9d6972120781b974380bfa162008"}, ] -[package.dependencies] -typing_extensions = {version = "*", markers = "python_version < \"3.10\""} - [package.extras] dev = ["backports.zoneinfo", "black", "build", "freezegun", "mdx_truly_sane_lists", "mike", "mkdocs", "mkdocs-awesome-pages-plugin", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material (>=8.5)", "mkdocstrings[python]", "msgspec", "msgspec-python313-pre", "mypy", "orjson", "pylint", "pytest", "tzdata", "validate-pyproject[all]"] @@ -6192,13 +6164,13 @@ files = [ [[package]] name = "python-multipart" -version = "0.0.17" +version = "0.0.20" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" files = [ - {file = "python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d"}, - {file = "python_multipart-0.0.17.tar.gz", hash = "sha256:41330d831cae6e2f22902704ead2826ea038d0419530eadff3ea80175aec5538"}, + {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, + {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, ] [[package]] @@ -6486,27 +6458,30 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "qdrant-client" -version = "1.12.1" +version = "1.12.2" description = "Client library for the Qdrant vector search engine" optional = true -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "qdrant_client-1.12.1-py3-none-any.whl", hash = "sha256:b2d17ce18e9e767471368380dd3bbc4a0e3a0e2061fedc9af3542084b48451e0"}, - {file = "qdrant_client-1.12.1.tar.gz", hash = "sha256:35e8e646f75b7b883b3d2d0ee4c69c5301000bba41c82aa546e985db0f1aeb72"}, + {file = "qdrant_client-1.12.2-py3-none-any.whl", hash = "sha256:a0ae500a46a679ff3521ba3f1f1cf3d72b57090a768cec65fc317066bcbac1e6"}, + {file = "qdrant_client-1.12.2.tar.gz", hash = "sha256:2777e09b3e89bb22bb490384d8b1fa8140f3915287884f18984f7031a346aba5"}, ] [package.dependencies] grpcio = ">=1.41.0" grpcio-tools = ">=1.41.0" httpx = {version = ">=0.20.0", extras = ["http2"]} -numpy = {version = ">=1.21", markers = "python_version >= \"3.8\" and python_version < \"3.12\""} +numpy = [ + {version = ">=1.21", markers = "python_version >= \"3.10\" and python_version < \"3.12\""}, + {version = ">=1.26", markers = "python_version >= \"3.12\" and python_version < \"3.13\""}, +] portalocker = ">=2.7.0,<3.0.0" pydantic = ">=1.10.8" urllib3 = ">=1.26.14,<3" [package.extras] -fastembed = ["fastembed (==0.3.6)"] -fastembed-gpu = ["fastembed-gpu (==0.3.6)"] +fastembed = ["fastembed (==0.5.0)"] +fastembed-gpu = ["fastembed-gpu (==0.5.0)"] [[package]] name = "rapidfuzz" @@ -6970,29 +6945,29 @@ files = [ [[package]] name = "ruff" -version = "0.8.4" +version = "0.8.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60"}, - {file = "ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac"}, - {file = "ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720"}, - {file = "ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae"}, - {file = "ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7"}, - {file = "ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111"}, - {file = "ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8"}, - {file = "ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835"}, - {file = "ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d"}, - {file = "ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08"}, - {file = "ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8"}, + {file = "ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3"}, + {file = "ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1"}, + {file = "ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76"}, + {file = "ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764"}, + {file = "ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905"}, + {file = "ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162"}, + {file = "ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5"}, ] [[package]] @@ -7014,121 +6989,26 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "safetensors" -version = "0.4.5" +version = "0.5.1" description = "" optional = false python-versions = ">=3.7" files = [ - {file = "safetensors-0.4.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a63eaccd22243c67e4f2b1c3e258b257effc4acd78f3b9d397edc8cf8f1298a7"}, - {file = "safetensors-0.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:23fc9b4ec7b602915cbb4ec1a7c1ad96d2743c322f20ab709e2c35d1b66dad27"}, - {file = "safetensors-0.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6885016f34bef80ea1085b7e99b3c1f92cb1be78a49839203060f67b40aee761"}, - {file = "safetensors-0.4.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:133620f443450429322f238fda74d512c4008621227fccf2f8cf4a76206fea7c"}, - {file = "safetensors-0.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4fb3e0609ec12d2a77e882f07cced530b8262027f64b75d399f1504ffec0ba56"}, - {file = "safetensors-0.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0f1dd769f064adc33831f5e97ad07babbd728427f98e3e1db6902e369122737"}, - {file = "safetensors-0.4.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6d156bdb26732feada84f9388a9f135528c1ef5b05fae153da365ad4319c4c5"}, - {file = "safetensors-0.4.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e347d77e2c77eb7624400ccd09bed69d35c0332f417ce8c048d404a096c593b"}, - {file = "safetensors-0.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9f556eea3aec1d3d955403159fe2123ddd68e880f83954ee9b4a3f2e15e716b6"}, - {file = "safetensors-0.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9483f42be3b6bc8ff77dd67302de8ae411c4db39f7224dec66b0eb95822e4163"}, - {file = "safetensors-0.4.5-cp310-none-win32.whl", hash = "sha256:7389129c03fadd1ccc37fd1ebbc773f2b031483b04700923c3511d2a939252cc"}, - {file = "safetensors-0.4.5-cp310-none-win_amd64.whl", hash = "sha256:e98ef5524f8b6620c8cdef97220c0b6a5c1cef69852fcd2f174bb96c2bb316b1"}, - {file = "safetensors-0.4.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:21f848d7aebd5954f92538552d6d75f7c1b4500f51664078b5b49720d180e47c"}, - {file = "safetensors-0.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb07000b19d41e35eecef9a454f31a8b4718a185293f0d0b1c4b61d6e4487971"}, - {file = "safetensors-0.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09dedf7c2fda934ee68143202acff6e9e8eb0ddeeb4cfc24182bef999efa9f42"}, - {file = "safetensors-0.4.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59b77e4b7a708988d84f26de3ebead61ef1659c73dcbc9946c18f3b1786d2688"}, - {file = "safetensors-0.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d3bc83e14d67adc2e9387e511097f254bd1b43c3020440e708858c684cbac68"}, - {file = "safetensors-0.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39371fc551c1072976073ab258c3119395294cf49cdc1f8476794627de3130df"}, - {file = "safetensors-0.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6c19feda32b931cae0acd42748a670bdf56bee6476a046af20181ad3fee4090"}, - {file = "safetensors-0.4.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a659467495de201e2f282063808a41170448c78bada1e62707b07a27b05e6943"}, - {file = "safetensors-0.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bad5e4b2476949bcd638a89f71b6916fa9a5cae5c1ae7eede337aca2100435c0"}, - {file = "safetensors-0.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a3a315a6d0054bc6889a17f5668a73f94f7fe55121ff59e0a199e3519c08565f"}, - {file = "safetensors-0.4.5-cp311-none-win32.whl", hash = "sha256:a01e232e6d3d5cf8b1667bc3b657a77bdab73f0743c26c1d3c5dd7ce86bd3a92"}, - {file = "safetensors-0.4.5-cp311-none-win_amd64.whl", hash = "sha256:cbd39cae1ad3e3ef6f63a6f07296b080c951f24cec60188378e43d3713000c04"}, - {file = "safetensors-0.4.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:473300314e026bd1043cef391bb16a8689453363381561b8a3e443870937cc1e"}, - {file = "safetensors-0.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:801183a0f76dc647f51a2d9141ad341f9665602a7899a693207a82fb102cc53e"}, - {file = "safetensors-0.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1524b54246e422ad6fb6aea1ac71edeeb77666efa67230e1faf6999df9b2e27f"}, - {file = "safetensors-0.4.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3139098e3e8b2ad7afbca96d30ad29157b50c90861084e69fcb80dec7430461"}, - {file = "safetensors-0.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65573dc35be9059770808e276b017256fa30058802c29e1038eb1c00028502ea"}, - {file = "safetensors-0.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd33da8e9407559f8779c82a0448e2133737f922d71f884da27184549416bfed"}, - {file = "safetensors-0.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3685ce7ed036f916316b567152482b7e959dc754fcc4a8342333d222e05f407c"}, - {file = "safetensors-0.4.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dde2bf390d25f67908278d6f5d59e46211ef98e44108727084d4637ee70ab4f1"}, - {file = "safetensors-0.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7469d70d3de970b1698d47c11ebbf296a308702cbaae7fcb993944751cf985f4"}, - {file = "safetensors-0.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a6ba28118636a130ccbb968bc33d4684c48678695dba2590169d5ab03a45646"}, - {file = "safetensors-0.4.5-cp312-none-win32.whl", hash = "sha256:c859c7ed90b0047f58ee27751c8e56951452ed36a67afee1b0a87847d065eec6"}, - {file = "safetensors-0.4.5-cp312-none-win_amd64.whl", hash = "sha256:b5a8810ad6a6f933fff6c276eae92c1da217b39b4d8b1bc1c0b8af2d270dc532"}, - {file = "safetensors-0.4.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:25e5f8e2e92a74f05b4ca55686234c32aac19927903792b30ee6d7bd5653d54e"}, - {file = "safetensors-0.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81efb124b58af39fcd684254c645e35692fea81c51627259cdf6d67ff4458916"}, - {file = "safetensors-0.4.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:585f1703a518b437f5103aa9cf70e9bd437cb78eea9c51024329e4fb8a3e3679"}, - {file = "safetensors-0.4.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b99fbf72e3faf0b2f5f16e5e3458b93b7d0a83984fe8d5364c60aa169f2da89"}, - {file = "safetensors-0.4.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b17b299ca9966ca983ecda1c0791a3f07f9ca6ab5ded8ef3d283fff45f6bcd5f"}, - {file = "safetensors-0.4.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76ded72f69209c9780fdb23ea89e56d35c54ae6abcdec67ccb22af8e696e449a"}, - {file = "safetensors-0.4.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2783956926303dcfeb1de91a4d1204cd4089ab441e622e7caee0642281109db3"}, - {file = "safetensors-0.4.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d94581aab8c6b204def4d7320f07534d6ee34cd4855688004a4354e63b639a35"}, - {file = "safetensors-0.4.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:67e1e7cb8678bb1b37ac48ec0df04faf689e2f4e9e81e566b5c63d9f23748523"}, - {file = "safetensors-0.4.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:dbd280b07e6054ea68b0cb4b16ad9703e7d63cd6890f577cb98acc5354780142"}, - {file = "safetensors-0.4.5-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:77d9b228da8374c7262046a36c1f656ba32a93df6cc51cd4453af932011e77f1"}, - {file = "safetensors-0.4.5-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:500cac01d50b301ab7bb192353317035011c5ceeef0fca652f9f43c000bb7f8d"}, - {file = "safetensors-0.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75331c0c746f03158ded32465b7d0b0e24c5a22121743662a2393439c43a45cf"}, - {file = "safetensors-0.4.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:670e95fe34e0d591d0529e5e59fd9d3d72bc77b1444fcaa14dccda4f36b5a38b"}, - {file = "safetensors-0.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:098923e2574ff237c517d6e840acada8e5b311cb1fa226019105ed82e9c3b62f"}, - {file = "safetensors-0.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ca0902d2648775089fa6a0c8fc9e6390c5f8ee576517d33f9261656f851e3f"}, - {file = "safetensors-0.4.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f0032bedc869c56f8d26259fe39cd21c5199cd57f2228d817a0e23e8370af25"}, - {file = "safetensors-0.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f4b15f51b4f8f2a512341d9ce3475cacc19c5fdfc5db1f0e19449e75f95c7dc8"}, - {file = "safetensors-0.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f6594d130d0ad933d885c6a7b75c5183cb0e8450f799b80a39eae2b8508955eb"}, - {file = "safetensors-0.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:60c828a27e852ded2c85fc0f87bf1ec20e464c5cd4d56ff0e0711855cc2e17f8"}, - {file = "safetensors-0.4.5-cp37-none-win32.whl", hash = "sha256:6d3de65718b86c3eeaa8b73a9c3d123f9307a96bbd7be9698e21e76a56443af5"}, - {file = "safetensors-0.4.5-cp37-none-win_amd64.whl", hash = "sha256:5a2d68a523a4cefd791156a4174189a4114cf0bf9c50ceb89f261600f3b2b81a"}, - {file = "safetensors-0.4.5-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:e7a97058f96340850da0601a3309f3d29d6191b0702b2da201e54c6e3e44ccf0"}, - {file = "safetensors-0.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:63bfd425e25f5c733f572e2246e08a1c38bd6f2e027d3f7c87e2e43f228d1345"}, - {file = "safetensors-0.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3664ac565d0e809b0b929dae7ccd74e4d3273cd0c6d1220c6430035befb678e"}, - {file = "safetensors-0.4.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:313514b0b9b73ff4ddfb4edd71860696dbe3c1c9dc4d5cc13dbd74da283d2cbf"}, - {file = "safetensors-0.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31fa33ee326f750a2f2134a6174773c281d9a266ccd000bd4686d8021f1f3dac"}, - {file = "safetensors-0.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09566792588d77b68abe53754c9f1308fadd35c9f87be939e22c623eaacbed6b"}, - {file = "safetensors-0.4.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309aaec9b66cbf07ad3a2e5cb8a03205663324fea024ba391594423d0f00d9fe"}, - {file = "safetensors-0.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53946c5813b8f9e26103c5efff4a931cc45d874f45229edd68557ffb35ffb9f8"}, - {file = "safetensors-0.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:868f9df9e99ad1e7f38c52194063a982bc88fedc7d05096f4f8160403aaf4bd6"}, - {file = "safetensors-0.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9cc9449bd0b0bc538bd5e268221f0c5590bc5c14c1934a6ae359d44410dc68c4"}, - {file = "safetensors-0.4.5-cp38-none-win32.whl", hash = "sha256:83c4f13a9e687335c3928f615cd63a37e3f8ef072a3f2a0599fa09f863fb06a2"}, - {file = "safetensors-0.4.5-cp38-none-win_amd64.whl", hash = "sha256:b98d40a2ffa560653f6274e15b27b3544e8e3713a44627ce268f419f35c49478"}, - {file = "safetensors-0.4.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cf727bb1281d66699bef5683b04d98c894a2803442c490a8d45cd365abfbdeb2"}, - {file = "safetensors-0.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96f1d038c827cdc552d97e71f522e1049fef0542be575421f7684756a748e457"}, - {file = "safetensors-0.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:139fbee92570ecea774e6344fee908907db79646d00b12c535f66bc78bd5ea2c"}, - {file = "safetensors-0.4.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c36302c1c69eebb383775a89645a32b9d266878fab619819ce660309d6176c9b"}, - {file = "safetensors-0.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d641f5b8149ea98deb5ffcf604d764aad1de38a8285f86771ce1abf8e74c4891"}, - {file = "safetensors-0.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b4db6a61d968de73722b858038c616a1bebd4a86abe2688e46ca0cc2d17558f2"}, - {file = "safetensors-0.4.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b75a616e02f21b6f1d5785b20cecbab5e2bd3f6358a90e8925b813d557666ec1"}, - {file = "safetensors-0.4.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:788ee7d04cc0e0e7f944c52ff05f52a4415b312f5efd2ee66389fb7685ee030c"}, - {file = "safetensors-0.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87bc42bd04fd9ca31396d3ca0433db0be1411b6b53ac5a32b7845a85d01ffc2e"}, - {file = "safetensors-0.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4037676c86365a721a8c9510323a51861d703b399b78a6b4486a54a65a975fca"}, - {file = "safetensors-0.4.5-cp39-none-win32.whl", hash = "sha256:1500418454529d0ed5c1564bda376c4ddff43f30fce9517d9bee7bcce5a8ef50"}, - {file = "safetensors-0.4.5-cp39-none-win_amd64.whl", hash = "sha256:9d1a94b9d793ed8fe35ab6d5cea28d540a46559bafc6aae98f30ee0867000cab"}, - {file = "safetensors-0.4.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fdadf66b5a22ceb645d5435a0be7a0292ce59648ca1d46b352f13cff3ea80410"}, - {file = "safetensors-0.4.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d42ffd4c2259f31832cb17ff866c111684c87bd930892a1ba53fed28370c918c"}, - {file = "safetensors-0.4.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd8a1f6d2063a92cd04145c7fd9e31a1c7d85fbec20113a14b487563fdbc0597"}, - {file = "safetensors-0.4.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:951d2fcf1817f4fb0ef0b48f6696688a4e852a95922a042b3f96aaa67eedc920"}, - {file = "safetensors-0.4.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ac85d9a8c1af0e3132371d9f2d134695a06a96993c2e2f0bbe25debb9e3f67a"}, - {file = "safetensors-0.4.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e3cec4a29eb7fe8da0b1c7988bc3828183080439dd559f720414450de076fcab"}, - {file = "safetensors-0.4.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:21742b391b859e67b26c0b2ac37f52c9c0944a879a25ad2f9f9f3cd61e7fda8f"}, - {file = "safetensors-0.4.5-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c7db3006a4915151ce1913652e907cdede299b974641a83fbc092102ac41b644"}, - {file = "safetensors-0.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f68bf99ea970960a237f416ea394e266e0361895753df06e3e06e6ea7907d98b"}, - {file = "safetensors-0.4.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8158938cf3324172df024da511839d373c40fbfaa83e9abf467174b2910d7b4c"}, - {file = "safetensors-0.4.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:540ce6c4bf6b58cb0fd93fa5f143bc0ee341c93bb4f9287ccd92cf898cc1b0dd"}, - {file = "safetensors-0.4.5-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bfeaa1a699c6b9ed514bd15e6a91e74738b71125a9292159e3d6b7f0a53d2cde"}, - {file = "safetensors-0.4.5-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:01c8f00da537af711979e1b42a69a8ec9e1d7112f208e0e9b8a35d2c381085ef"}, - {file = "safetensors-0.4.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a0dd565f83b30f2ca79b5d35748d0d99dd4b3454f80e03dfb41f0038e3bdf180"}, - {file = "safetensors-0.4.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:023b6e5facda76989f4cba95a861b7e656b87e225f61811065d5c501f78cdb3f"}, - {file = "safetensors-0.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9633b663393d5796f0b60249549371e392b75a0b955c07e9c6f8708a87fc841f"}, - {file = "safetensors-0.4.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78dd8adfb48716233c45f676d6e48534d34b4bceb50162c13d1f0bdf6f78590a"}, - {file = "safetensors-0.4.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e8deb16c4321d61ae72533b8451ec4a9af8656d1c61ff81aa49f966406e4b68"}, - {file = "safetensors-0.4.5-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:52452fa5999dc50c4decaf0c53aa28371f7f1e0fe5c2dd9129059fbe1e1599c7"}, - {file = "safetensors-0.4.5-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d5f23198821e227cfc52d50fa989813513db381255c6d100927b012f0cfec63d"}, - {file = "safetensors-0.4.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f4beb84b6073b1247a773141a6331117e35d07134b3bb0383003f39971d414bb"}, - {file = "safetensors-0.4.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:68814d599d25ed2fdd045ed54d370d1d03cf35e02dce56de44c651f828fb9b7b"}, - {file = "safetensors-0.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b6453c54c57c1781292c46593f8a37254b8b99004c68d6c3ce229688931a22"}, - {file = "safetensors-0.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adaa9c6dead67e2dd90d634f89131e43162012479d86e25618e821a03d1eb1dc"}, - {file = "safetensors-0.4.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73e7d408e9012cd17511b382b43547850969c7979efc2bc353f317abaf23c84c"}, - {file = "safetensors-0.4.5-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:775409ce0fcc58b10773fdb4221ed1eb007de10fe7adbdf8f5e8a56096b6f0bc"}, - {file = "safetensors-0.4.5-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:834001bed193e4440c4a3950a31059523ee5090605c907c66808664c932b549c"}, - {file = "safetensors-0.4.5.tar.gz", hash = "sha256:d73de19682deabb02524b3d5d1f8b3aaba94c72f1bbfc7911b9b9d5d391c0310"}, + {file = "safetensors-0.5.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5480b078590dd37ee1c27f153e1ee9a274b62b30871ee16c412d11341215f305"}, + {file = "safetensors-0.5.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:547e9fe8f3c9c50caf07cfcb6d2392f511853f7041821812ba73a05a915e91dd"}, + {file = "safetensors-0.5.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e98f5dbce744a87a8d2cb9147558e80af79cfe31aa4321554e1db0e49d9c957"}, + {file = "safetensors-0.5.1-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c40ef845cca82e365b46e192b7b4952082952d5965c602e030a73155336de89c"}, + {file = "safetensors-0.5.1-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3cb212b0cded22fa0e46bca248beecf2fd079f2dffd7cc04e116a8b0128ae601"}, + {file = "safetensors-0.5.1-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abb7bcf2faba63a0b58a2c6fafab0200726727ab6f579a1155239927a792709"}, + {file = "safetensors-0.5.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83a384e49b38c3ae3c02a52437548351af83029dff85fe3d1acd5b2cf06867bb"}, + {file = "safetensors-0.5.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:44946151b01083fe5863c20d626f6ed9f1544be80e3bb2177a7ec27f911fdbf8"}, + {file = "safetensors-0.5.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:168e7a032c944eb5aefaee0d4bf4e15e84dbbf0f2ef86fbe0dc778a68306fff8"}, + {file = "safetensors-0.5.1-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:430b7eab6b4139bee8587522f264f7eebbac3e41614b52e35caf90affe7e7972"}, + {file = "safetensors-0.5.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:97f548d6e9f86d3326ab8416303f9ae1ded15df126b87db42658c3d89a1040d7"}, + {file = "safetensors-0.5.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b021cad4af26677e0d3fbc5c8e2dfc3087ac44a3e0450576cbe0aa165849578c"}, + {file = "safetensors-0.5.1-cp38-abi3-win32.whl", hash = "sha256:7290f8acdf1e5b5daf6101d6eed506d1f6ad66d08ca9f26235372befba7e2285"}, + {file = "safetensors-0.5.1-cp38-abi3-win_amd64.whl", hash = "sha256:895f33c8ee55310606a407f45de3468ec0ffe259ba53cc0d4024a64fb58a1fc9"}, + {file = "safetensors-0.5.1.tar.gz", hash = "sha256:75927919a73b0f34d6943b531d757f724e65797a900d88d8081fe8b4448eadc3"}, ] [package.extras] @@ -7138,7 +7018,7 @@ jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "safetensors[num mlx = ["mlx (>=0.0.9)"] numpy = ["numpy (>=1.21.6)"] paddlepaddle = ["paddlepaddle (>=2.4.1)", "safetensors[numpy]"] -pinned-tf = ["safetensors[numpy]", "tensorflow (==2.11.0)"] +pinned-tf = ["safetensors[numpy]", "tensorflow (==2.18.0)"] quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools-rust (>=1.5.2)"] @@ -7200,45 +7080,60 @@ tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc ( [[package]] name = "scipy" -version = "1.13.1" +version = "1.15.0" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = ">=3.9" -files = [ - {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, - {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, - {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, - {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, - {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, - {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, - {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, - {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, - {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, - {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, - {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, - {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, - {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, - {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, - {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, - {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, - {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, - {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, - {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, - {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, - {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, - {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, - {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, - {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, - {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, -] - -[package.dependencies] -numpy = ">=1.22.4,<2.3" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] -test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +python-versions = ">=3.10" +files = [ + {file = "scipy-1.15.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:aeac60d3562a7bf2f35549bdfdb6b1751c50590f55ce7322b4b2fc821dc27fca"}, + {file = "scipy-1.15.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5abbdc6ede5c5fed7910cf406a948e2c0869231c0db091593a6b2fa78be77e5d"}, + {file = "scipy-1.15.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:eb1533c59f0ec6c55871206f15a5c72d1fae7ad3c0a8ca33ca88f7c309bbbf8c"}, + {file = "scipy-1.15.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:de112c2dae53107cfeaf65101419662ac0a54e9a088c17958b51c95dac5de56d"}, + {file = "scipy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2240e1fd0782e62e1aacdc7234212ee271d810f67e9cd3b8d521003a82603ef8"}, + {file = "scipy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d35aef233b098e4de88b1eac29f0df378278e7e250a915766786b773309137c4"}, + {file = "scipy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b29e4fc02e155a5fd1165f1e6a73edfdd110470736b0f48bcbe48083f0eee37"}, + {file = "scipy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e5b34f8894f9904cc578008d1a9467829c1817e9f9cb45e6d6eeb61d2ab7731"}, + {file = "scipy-1.15.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:46e91b5b16909ff79224b56e19cbad65ca500b3afda69225820aa3afbf9ec020"}, + {file = "scipy-1.15.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:82bff2eb01ccf7cea8b6ee5274c2dbeadfdac97919da308ee6d8e5bcbe846443"}, + {file = "scipy-1.15.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:9c8254fe21dd2c6c8f7757035ec0c31daecf3bb3cffd93bc1ca661b731d28136"}, + {file = "scipy-1.15.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:c9624eeae79b18cab1a31944b5ef87aa14b125d6ab69b71db22f0dbd962caf1e"}, + {file = "scipy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d13bbc0658c11f3d19df4138336e4bce2c4fbd78c2755be4bf7b8e235481557f"}, + {file = "scipy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdca4c7bb8dc41307e5f39e9e5d19c707d8e20a29845e7533b3bb20a9d4ccba0"}, + {file = "scipy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f376d7c767731477bac25a85d0118efdc94a572c6b60decb1ee48bf2391a73b"}, + {file = "scipy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:61513b989ee8d5218fbeb178b2d51534ecaddba050db949ae99eeb3d12f6825d"}, + {file = "scipy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5beb0a2200372b7416ec73fdae94fe81a6e85e44eb49c35a11ac356d2b8eccc6"}, + {file = "scipy-1.15.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fde0f3104dfa1dfbc1f230f65506532d0558d43188789eaf68f97e106249a913"}, + {file = "scipy-1.15.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:35c68f7044b4e7ad73a3e68e513dda946989e523df9b062bd3cf401a1a882192"}, + {file = "scipy-1.15.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:52475011be29dfcbecc3dfe3060e471ac5155d72e9233e8d5616b84e2b542054"}, + {file = "scipy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5972e3f96f7dda4fd3bb85906a17338e65eaddfe47f750e240f22b331c08858e"}, + {file = "scipy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00169cf875bed0b3c40e4da45b57037dc21d7c7bf0c85ed75f210c281488f1"}, + {file = "scipy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:161f80a98047c219c257bf5ce1777c574bde36b9d962a46b20d0d7e531f86863"}, + {file = "scipy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:327163ad73e54541a675240708244644294cb0a65cca420c9c79baeb9648e479"}, + {file = "scipy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0fcb16eb04d84670722ce8d93b05257df471704c913cb0ff9dc5a1c31d1e9422"}, + {file = "scipy-1.15.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:767e8cf6562931f8312f4faa7ddea412cb783d8df49e62c44d00d89f41f9bbe8"}, + {file = "scipy-1.15.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:37ce9394cdcd7c5f437583fc6ef91bd290014993900643fdfc7af9b052d1613b"}, + {file = "scipy-1.15.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6d26f17c64abd6c6c2dfb39920f61518cc9e213d034b45b2380e32ba78fde4c0"}, + {file = "scipy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e2448acd79c6374583581a1ded32ac71a00c2b9c62dfa87a40e1dd2520be111"}, + {file = "scipy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36be480e512d38db67f377add5b759fb117edd987f4791cdf58e59b26962bee4"}, + {file = "scipy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ccb6248a9987193fe74363a2d73b93bc2c546e0728bd786050b7aef6e17db03c"}, + {file = "scipy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:952d2e9eaa787f0a9e95b6e85da3654791b57a156c3e6609e65cc5176ccfe6f2"}, + {file = "scipy-1.15.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b1432102254b6dc7766d081fa92df87832ac25ff0b3d3a940f37276e63eb74ff"}, + {file = "scipy-1.15.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:4e08c6a36f46abaedf765dd2dfcd3698fa4bd7e311a9abb2d80e33d9b2d72c34"}, + {file = "scipy-1.15.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ec915cd26d76f6fc7ae8522f74f5b2accf39546f341c771bb2297f3871934a52"}, + {file = "scipy-1.15.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:351899dd2a801edd3691622172bc8ea01064b1cada794f8641b89a7dc5418db6"}, + {file = "scipy-1.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9baff912ea4f78a543d183ed6f5b3bea9784509b948227daaf6f10727a0e2e5"}, + {file = "scipy-1.15.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cd9d9198a7fd9a77f0eb5105ea9734df26f41faeb2a88a0e62e5245506f7b6df"}, + {file = "scipy-1.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:129f899ed275c0515d553b8d31696924e2ca87d1972421e46c376b9eb87de3d2"}, + {file = "scipy-1.15.0.tar.gz", hash = "sha256:300742e2cc94e36a2880ebe464a1c8b4352a7b0f3e36ec3d2ac006cdbe0219ac"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.5" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.16.5)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "semver" @@ -7324,23 +7219,23 @@ tornado = ["tornado (>=6)"] [[package]] name = "setuptools" -version = "75.6.0" +version = "75.7.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" files = [ - {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, - {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, + {file = "setuptools-75.7.0-py3-none-any.whl", hash = "sha256:84fb203f278ebcf5cd08f97d3fb96d3fbed4b629d500b29ad60d11e00769b183"}, + {file = "setuptools-75.7.0.tar.gz", hash = "sha256:886ff7b16cd342f1d1defc16fc98c9ce3fde69e087a4e1983d7ab634e5f41f4f"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.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)"] 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 (>=5.5)", "packaging (>=24.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.12,<1.14)", "pytest-mypy"] +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.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.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.14.*)", "pytest-mypy"] [[package]] name = "shellingham" @@ -7485,13 +7380,13 @@ files = [ [[package]] name = "smmap" -version = "5.0.1" +version = "5.0.2" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.7" files = [ - {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, - {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, + {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, + {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, ] [[package]] @@ -7518,60 +7413,68 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.35" +version = "2.0.36" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-win32.whl", hash = "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-win_amd64.whl", hash = "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-win32.whl", hash = "sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-win_amd64.whl", hash = "sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-win32.whl", hash = "sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-win_amd64.whl", hash = "sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f021d334f2ca692523aaf7bbf7592ceff70c8594fad853416a81d66b35e3abf9"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05c3f58cf91683102f2f0265c0db3bd3892e9eedabe059720492dbaa4f922da1"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:032d979ce77a6c2432653322ba4cbeabf5a6837f704d16fa38b5a05d8e21fa00"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:2e795c2f7d7249b75bb5f479b432a51b59041580d20599d4e112b5f2046437a3"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:cc32b2990fc34380ec2f6195f33a76b6cdaa9eecf09f0c9404b74fc120aef36f"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-win32.whl", hash = "sha256:9509c4123491d0e63fb5e16199e09f8e262066e58903e84615c301dde8fa2e87"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-win_amd64.whl", hash = "sha256:3655af10ebcc0f1e4e06c5900bb33e080d6a1fa4228f502121f28a3b1753cde5"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4c31943b61ed8fdd63dfd12ccc919f2bf95eefca133767db6fbbd15da62078ec"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a62dd5d7cc8626a3634208df458c5fe4f21200d96a74d122c83bc2015b333bc1"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0630774b0977804fba4b6bbea6852ab56c14965a2b0c7fc7282c5f7d90a1ae72"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d625eddf7efeba2abfd9c014a22c0f6b3796e0ffb48f5d5ab106568ef01ff5a"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ada603db10bb865bbe591939de854faf2c60f43c9b763e90f653224138f910d9"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c41411e192f8d3ea39ea70e0fae48762cd11a2244e03751a98bd3c0ca9a4e936"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-win32.whl", hash = "sha256:d299797d75cd747e7797b1b41817111406b8b10a4f88b6e8fe5b5e59598b43b0"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-win_amd64.whl", hash = "sha256:0375a141e1c0878103eb3d719eb6d5aa444b490c96f3fedab8471c7f6ffe70ee"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccae5de2a0140d8be6838c331604f91d6fafd0735dbdcee1ac78fc8fbaba76b4"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a275a806f73e849e1c309ac11108ea1a14cd7058577aba962cd7190e27c9e3c"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:732e026240cdd1c1b2e3ac515c7a23820430ed94292ce33806a95869c46bd139"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:890da8cd1941fa3dab28c5bac3b9da8502e7e366f895b3b8e500896f12f94d11"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0d8326269dbf944b9201911b0d9f3dc524d64779a07518199a58384c3d37a44"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b76d63495b0508ab9fc23f8152bac63205d2a704cd009a2b0722f4c8e0cba8e0"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-win32.whl", hash = "sha256:69683e02e8a9de37f17985905a5eca18ad651bf592314b4d3d799029797d0eb3"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-win_amd64.whl", hash = "sha256:aee110e4ef3c528f3abbc3c2018c121e708938adeeff9006428dd7c8555e9b3f"}, - {file = "SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1"}, - {file = "sqlalchemy-2.0.35.tar.gz", hash = "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, + {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, + {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, ] [package.dependencies] @@ -7584,7 +7487,7 @@ aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] @@ -7646,7 +7549,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"] @@ -7667,13 +7569,13 @@ widechars = ["wcwidth"] [[package]] name = "tenacity" -version = "8.5.0" +version = "9.0.0" description = "Retry code until it succeeds" optional = false python-versions = ">=3.8" files = [ - {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, - {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, + {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, + {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, ] [package.extras] @@ -7870,7 +7772,7 @@ files = [ name = "tornado" version = "6.4.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, @@ -8044,13 +7946,13 @@ files = [ [[package]] name = "types-setuptools" -version = "75.6.0.20241126" +version = "75.6.0.20241223" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.8" files = [ - {file = "types_setuptools-75.6.0.20241126-py3-none-any.whl", hash = "sha256:aaae310a0e27033c1da8457d4d26ac673b0c8a0de7272d6d4708e263f2ea3b9b"}, - {file = "types_setuptools-75.6.0.20241126.tar.gz", hash = "sha256:7bf25ad4be39740e469f9268b6beddda6e088891fa5a27e985c6ce68bf62ace0"}, + {file = "types_setuptools-75.6.0.20241223-py3-none-any.whl", hash = "sha256:7cbfd3bf2944f88bbcdd321b86ddd878232a277be95d44c78a53585d78ebc2f6"}, + {file = "types_setuptools-75.6.0.20241223.tar.gz", hash = "sha256:d9478a985057ed48a994c707f548e55aababa85fe1c9b212f43ab5a1fffd3211"}, ] [[package]] @@ -8179,13 +8081,13 @@ files = [ [[package]] name = "unstructured" -version = "0.16.11" +version = "0.16.12" description = "A library that prepares raw documents for downstream ML tasks." optional = true python-versions = "<3.13,>=3.9.0" files = [ - {file = "unstructured-0.16.11-py3-none-any.whl", hash = "sha256:a92d5bc2c2b7bb23369641fb7a7f0daba1775639199306ce4cd83ca564a03763"}, - {file = "unstructured-0.16.11.tar.gz", hash = "sha256:33ebf68aae11ce33c8a96335296557b5abd8ba96eaba3e5a1554c0b9eee40bb5"}, + {file = "unstructured-0.16.12-py3-none-any.whl", hash = "sha256:bcac29ac1b38fba4228c5a1a7721d1aa7c48220f7c1dd43b563645c56e978c49"}, + {file = "unstructured-0.16.12.tar.gz", hash = "sha256:c3133731c6edb9c2f474e62cb2b560cd0a8d578c4532ec14d8c0941e401770b0"}, ] [package.dependencies] @@ -8199,6 +8101,7 @@ html5lib = "*" langdetect = "*" lxml = "*" markdown = {version = "*", optional = true, markers = "extra == \"md\""} +ndjson = "*" networkx = {version = "*", optional = true, markers = "extra == \"xlsx\""} nltk = "*" numpy = "<2" @@ -8292,29 +8195,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake [[package]] name = "urllib3" -version = "1.26.20" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, - {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, -] - -[package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "urllib3" -version = "2.2.3" +version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] @@ -8357,13 +8244,13 @@ crypto-eth-addresses = ["eth-hash[pycryptodome] (>=0.7.0)"] [[package]] name = "virtualenv" -version = "20.28.0" +version = "20.28.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, - {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, + {file = "virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb"}, + {file = "virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329"}, ] [package.dependencies] @@ -8742,6 +8629,17 @@ files = [ {file = "xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f"}, ] +[[package]] +name = "xyzservices" +version = "2024.9.0" +description = "Source of XYZ tiles providers" +optional = false +python-versions = ">=3.8" +files = [ + {file = "xyzservices-2024.9.0-py3-none-any.whl", hash = "sha256:776ae82b78d6e5ca63dd6a94abb054df8130887a4a308473b54a6bd364de8644"}, + {file = "xyzservices-2024.9.0.tar.gz", hash = "sha256:68fb8353c9dbba4f1ff6c0f2e5e4e596bb9e1db7f94f4f7dfbcb26e25aa66fde"}, +] + [[package]] name = "yarl" version = "1.18.3" @@ -8876,5 +8774,5 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" -python-versions = ">=3.9.0,<3.12" -content-hash = "042e6fedc069e74ca4ea98cf4614bec99be59a90a29340520d189bf150fd2194" +python-versions = ">=3.10.0,<3.13" +content-hash = "6c1a7d6284b7cd7ce47110149f9796fc8d91efe7bef496edd92a706f2319b3d5" diff --git a/profiling/graph_pydantic_conversion/benchmark_function.py b/profiling/graph_pydantic_conversion/benchmark_function.py index 58990cc31..a4f5c839b 100644 --- a/profiling/graph_pydantic_conversion/benchmark_function.py +++ b/profiling/graph_pydantic_conversion/benchmark_function.py @@ -27,14 +27,11 @@ def benchmark_function(func: Callable, *args, num_runs: int = 5) -> Dict[str, An for _ in range(num_runs): # Start memory tracking tracemalloc.start() - initial_memory = process.memory_info().rss # Measure execution time and CPU usage start_time = time.perf_counter() start_cpu_time = process.cpu_times() - result = func(*args) - end_cpu_time = process.cpu_times() end_time = time.perf_counter() @@ -44,8 +41,6 @@ def benchmark_function(func: Callable, *args, num_runs: int = 5) -> Dict[str, An start_cpu_time.user + start_cpu_time.system ) current, peak = tracemalloc.get_traced_memory() - final_memory = process.memory_info().rss - memory_used = final_memory - initial_memory # Store results execution_times.append(execution_time) diff --git a/profiling/graph_pydantic_conversion/profile_graph_pydantic_conversion.py b/profiling/graph_pydantic_conversion/profile_graph_pydantic_conversion.py index f9047fc86..c1c0b6756 100644 --- a/profiling/graph_pydantic_conversion/profile_graph_pydantic_conversion.py +++ b/profiling/graph_pydantic_conversion/profile_graph_pydantic_conversion.py @@ -20,9 +20,7 @@ default=3, help="Recursive depth for graph generation (default: 3)", ) - parser.add_argument( - "--runs", type=int, default=5, help="Number of benchmark runs (default: 5)" - ) + parser.add_argument("--runs", type=int, default=5, help="Number of benchmark runs (default: 5)") args = parser.parse_args() society = create_organization_recursive( @@ -31,30 +29,32 @@ added_nodes = {} added_edges = {} visited_properties = {} - nodes, edges = asyncio.run(get_graph_from_model( - society, - added_nodes = added_nodes, - added_edges = added_edges, - visited_properties = visited_properties, - )) + nodes, edges = asyncio.run( + get_graph_from_model( + society, + added_nodes=added_nodes, + added_edges=added_edges, + visited_properties=visited_properties, + ) + ) def get_graph_from_model_sync(model): added_nodes = {} added_edges = {} visited_properties = {} - return asyncio.run(get_graph_from_model( - model, - added_nodes = added_nodes, - added_edges = added_edges, - visited_properties = visited_properties, - )) + return asyncio.run( + get_graph_from_model( + model, + added_nodes=added_nodes, + added_edges=added_edges, + visited_properties=visited_properties, + ) + ) results = benchmark_function(get_graph_from_model_sync, society, num_runs=args.runs) print("\nBenchmark Results:") - print( - f"N nodes: {len(nodes)}, N edges: {len(edges)}, Recursion depth: {args.recursive_depth}" - ) + print(f"N nodes: {len(nodes)}, N edges: {len(edges)}, Recursion depth: {args.recursive_depth}") print(f"Mean Peak Memory: {results['mean_peak_memory_mb']:.2f} MB") print(f"Mean CPU Usage: {results['mean_cpu_percent']:.2f}%") print(f"Mean Execution Time: {results['mean_execution_time']:.4f} seconds") diff --git a/profiling/util/DummyEmbeddingEngine.py b/profiling/util/DummyEmbeddingEngine.py index 7f5b3e847..0ba742182 100644 --- a/profiling/util/DummyEmbeddingEngine.py +++ b/profiling/util/DummyEmbeddingEngine.py @@ -1,9 +1,10 @@ import numpy as np from cognee.infrastructure.databases.vector.embeddings.EmbeddingEngine import EmbeddingEngine + class DummyEmbeddingEngine(EmbeddingEngine): async def embed_text(self, text: list[str]) -> list[list[float]]: - return(list(list(np.random.randn(3072)))) + return list(list(np.random.randn(3072))) def get_vector_size(self) -> int: - return(3072) + return 3072 diff --git a/profiling/util/DummyLLMAdapter.py b/profiling/util/DummyLLMAdapter.py index df81ce123..b28261665 100644 --- a/profiling/util/DummyLLMAdapter.py +++ b/profiling/util/DummyLLMAdapter.py @@ -15,15 +15,9 @@ class DummyLLMAdapter(LLMInterface): async def acreate_structured_output( self, text_input: str, system_prompt: str, response_model: Type[BaseModel] ) -> BaseModel: - - if ( - str(response_model) - == "" - ): + if str(response_model) == "": return dummy_summarize_content(text_input) - elif ( - str(response_model) == "" - ): + elif str(response_model) == "": return dummy_extract_knowledge_graph(text_input, self.nlp) else: raise Exception( diff --git a/pyproject.toml b/pyproject.toml index 8550a6edc..8349074f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,8 +18,8 @@ classifiers = [ ] [tool.poetry.dependencies] -python = ">=3.9.0,<3.12" -openai = "1.52.0" +python = ">=3.10.0,<3.13" +openai = "1.59.4" pydantic = "2.8.2" python-dotenv = "1.0.1" fastapi = ">=0.109.2,<0.116.0" @@ -34,18 +34,18 @@ falkordb = {version = "1.0.9", optional = true} boto3 = "^1.26.125" botocore="^1.35.54" gunicorn = "^20.1.0" -sqlalchemy = "2.0.35" -instructor = "1.5.2" +sqlalchemy = "2.0.36" +instructor = "1.7.2" networkx = "^3.2.1" aiosqlite = "^0.20.0" -pandas = "2.0.3" +pandas = "2.2.3" filetype = "^1.2.0" nltk = "^3.8.1" dlt = {extras = ["sqlalchemy"], version = "^1.4.1"} aiofiles = "^23.2.1" qdrant-client = {version = "^1.9.0", optional = true} graphistry = "^0.33.5" -tenacity = "^8.4.1" +tenacity = "^9.0.0" weaviate-client = {version = "4.9.6", optional = true} scikit-learn = "^1.5.0" pypdf = "^4.1.0" @@ -57,8 +57,8 @@ langchain_text_splitters = {version = "0.3.2", optional = true} langsmith = {version = "0.2.3", optional = true} langdetect = "1.0.9" posthog = {version = "^3.5.0", optional = true} -lancedb = "0.15.0" -litellm = "1.49.1" +lancedb = "0.16.0" +litellm = "1.57.2" groq = {version = "0.8.0", optional = true} langfuse = "^2.32.0" pydantic-settings = "^2.2.1" @@ -69,13 +69,16 @@ alembic = "^1.13.3" asyncpg = {version = "0.30.0", optional = true} pgvector = {version = "^0.3.5", optional = true} psycopg2 = {version = "^2.9.10", optional = true} -llama-index-core = {version = "^0.11.22", optional = true} +llama-index-core = {version = "^0.12.10.post1", optional = true} deepeval = {version = "^2.0.1", optional = true} transformers = "^4.46.3" pymilvus = {version = "^2.5.0", optional = true} unstructured = { extras = ["csv", "doc", "docx", "epub", "md", "odt", "org", "ppt", "pptx", "rst", "rtf", "tsv", "xlsx"], version = "^0.16.10", optional = true } pre-commit = "^4.0.1" httpx = "0.27.0" +bokeh="^3.6.2" + + @@ -108,6 +111,7 @@ pylint = "^3.0.3" ruff = ">=0.2.2,<0.9.0" tweepy = "4.14.0" gitpython = "^3.1.43" +pylance = "0.19.2" [tool.poetry.group.docs.dependencies] mkdocs-material = "^9.5.42" @@ -116,6 +120,16 @@ mkdocstrings = {extras = ["python"], version = "^0.26.2"} [tool.ruff] # https://beta.ruff.rs/docs/ line-length = 100 +exclude = [ + "migrations/", # Ignore migrations directory + "notebooks/", # Ignore notebook files + "build/", # Ignore build directory + "cognee/pipelines.py", + "cognee/modules/users/models/Group.py", + "cognee/modules/users/models/ACL.py", + "cognee/modules/pipelines/models/Task.py", + "cognee/modules/data/models/Dataset.py" +] [tool.ruff.lint] ignore = ["F401"] diff --git a/tests/import_test.py b/tests/import_test.py index 72034f4f0..0b44183cb 100644 --- a/tests/import_test.py +++ b/tests/import_test.py @@ -1,7 +1,7 @@ - def test_import_cognee(): try: import cognee + assert True # Pass the test if no error occurs except ImportError as e: assert False, f"Failed to import cognee: {e}" diff --git a/tools/check-lockfile.py b/tools/check-lockfile.py index 112387794..df60b35ac 100644 --- a/tools/check-lockfile.py +++ b/tools/check-lockfile.py @@ -7,19 +7,22 @@ try: count = 0 - with open(lockfile_name, 'r', encoding="utf8") as file: + with open(lockfile_name, "r", encoding="utf8") as file: for line in file: if hash_string in line: count += 1 if count >= threshold: - print(f"Success: Found '{hash_string}' more than {threshold} times in {lockfile_name}.") + print( + f"Success: Found '{hash_string}' more than {threshold} times in {lockfile_name}." + ) sys.exit(0) # If the loop completes without early exit, it means the threshold was not reached print( - f"Error: The string '{hash_string}' appears less than {threshold} times in {lockfile_name}, please make sure you are using an up to date poetry version.") + f"Error: The string '{hash_string}' appears less than {threshold} times in {lockfile_name}, please make sure you are using an up to date poetry version." + ) sys.exit(1) except FileNotFoundError: print(f"Error: File {lockfile_name} does not exist.") - sys.exit(1) \ No newline at end of file + sys.exit(1) diff --git a/tools/daily_pypi_downloads.py b/tools/daily_pypi_downloads.py deleted file mode 100644 index 64a0956ed..000000000 --- a/tools/daily_pypi_downloads.py +++ /dev/null @@ -1,81 +0,0 @@ -import uuid - -import requests -import posthog -import os -from datetime import datetime, timedelta - -# Replace with your PostHog Project API Key -POSTHOG_API_KEY = os.getenv('POSTHOG_API_KEY') -POSTHOG_API_HOST = 'https://eu.i.posthog.com' - -# Initialize PostHog client -posthog.project_api_key = POSTHOG_API_KEY -posthog.host = POSTHOG_API_HOST - -# Read last processed date from file -state_file = 'last_processed_date.txt' -if os.path.exists(state_file): - with open(state_file, 'r') as f: - last_processed_date = f.read().strip() - last_processed_date = datetime.strptime(last_processed_date, '%Y-%m-%d') -else: - # If no state file, start from 2 days ago - last_processed_date = datetime.utcnow() - timedelta(days=2) - -# Calculate the next date to process -next_date = last_processed_date + timedelta(days=1) -today = datetime.utcnow().date() - -if next_date.date() >= today: - print("No new data to process.") - exit(0) - -date_str = next_date.strftime('%Y-%m-%d') - -# Fetch download data for the date -package = 'cognee' -url = f'https://pypistats.org/api/packages/{package}/overall' - -response = requests.get(url) -if response.status_code != 200: - print(f"Failed to fetch data: {response.status_code}") - exit(1) - -data = response.json() - -# Find the entry for the date we want -downloads = None -for entry in data['data']: - if entry['date'] == date_str: - downloads = entry['downloads'] - category = entry.get('category') - break - -if downloads is None: - print(f"No data available for date {date_str}") - exit(1) - -# Create a unique message_id -message_id = f"cognee_downloads_{date_str}" - -distinct_id = str(uuid.uuid4()) - -# Send an event to PostHog -event_name = 'cognee_lib_downloads_with_mirrors' if category == 'with_mirrors' else 'cognee_lib_downloads_without_mirrors' - -if event_name == 'cognee_lib_downloads_without_mirrors': - posthog.capture( - distinct_id=str(uuid.uuid4()), - event=event_name, - properties={ - 'category': category, - 'date': date_str, - 'downloads': downloads, - } - ) -print(f"Data for {date_str} updated in PostHog successfully. Downloads is {downloads}") - -# Update the state file -with open(state_file, 'w') as f: - f.write(date_str) diff --git a/tools/push_to_posthog.py b/tools/push_to_posthog.py deleted file mode 100644 index 15cf05df6..000000000 --- a/tools/push_to_posthog.py +++ /dev/null @@ -1,76 +0,0 @@ -# extract_and_push_github_data.py -import uuid - -import requests -import os -from posthog import Posthog - -# Get environment variables -GITHUB_TOKEN = os.getenv('GITHUB_TOKEN') -REPO = os.getenv('GITHUB_REPOSITORY') -POSTHOG_API_KEY = os.getenv('POSTHOG_API_KEY') # Your PostHog Project API Key -POSTHOG_HOST = 'https://eu.i.posthog.com' # Default PostHog Cloud - -headers = { - "Authorization": f"token {GITHUB_TOKEN}", - "Accept": "application/vnd.github.v3+json" -} - -# Initialize PostHog client -posthog = Posthog( - api_key=POSTHOG_API_KEY, - host=POSTHOG_HOST -) - -posthog.debug = True - -def get_repo_info(): - url = f"https://api.github.com/repos/{REPO}" - response = requests.get(url, headers=headers) - if response.status_code == 200: - return response.json() - else: - print(f"Error fetching repo info: {response.status_code}") - return None - -def main(): - repo_info = get_repo_info() - - if repo_info: - # Prepare data to send to PostHog - properties = { - 'repo_name': repo_info.get('full_name'), - 'stars': repo_info.get('stargazers_count'), - 'forks': repo_info.get('forks_count'), - 'open_issues': repo_info.get('open_issues_count'), - 'watchers': repo_info.get('subscribers_count'), - 'created_at': repo_info.get('created_at'), - 'updated_at': repo_info.get('updated_at'), - 'pushed_at': repo_info.get('pushed_at'), - 'language': repo_info.get('language'), - 'license': repo_info.get('license').get('name') if repo_info.get('license') else None, - 'topics': repo_info.get('topics') - } - - print("Repository information: ", properties) - - distinct_id = str(uuid.uuid4()) - - # Send event to PostHog - result = posthog.capture( - distinct_id=distinct_id, # You can customize this identifier - event='cognee_lib_github_repo_stats', - properties=properties - ) - - print("PostHog response: ", result) - - print("Data sent to PostHog successfully.") - else: - print("Failed to retrieve repository information.") - - # Close PostHog client - posthog.shutdown() - -if __name__ == "__main__": - main()