migrate to pyright (#646)

* migrate to pyright

* Refactor type checking to use Pyright, update dependencies, and clean up code.

- Replaced MyPy with Pyright in configuration files and CI workflows.
- Updated `pyproject.toml` and `uv.lock` to reflect new dependencies and versions.
- Adjusted type hints and fixed minor code issues across various modules for better compatibility with Pyright.
- Added new packages `backoff` and `posthog` to the project dependencies.

* Update CI workflows to install all extra dependencies for type checking and unit tests

* Update dependencies in uv.lock to replace MyPy with Pyright and add nodeenv package. Adjust type hinting in config.py for compatibility with Pyright.
This commit is contained in:
Daniel Chalef 2025-06-30 12:04:21 -07:00 committed by GitHub
parent 7a8283dbac
commit 8213d10d44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 232 additions and 266 deletions

View file

@ -1,4 +1,4 @@
name: MyPy Type Check name: Pyright Type Check
on: on:
push: push:
@ -7,7 +7,7 @@ on:
branches: ["main"] branches: ["main"]
jobs: jobs:
mypy: pyright:
runs-on: depot-ubuntu-22.04 runs-on: depot-ubuntu-22.04
environment: development environment: development
steps: steps:
@ -22,26 +22,18 @@ jobs:
with: with:
version: "latest" version: "latest"
- name: Install dependencies - name: Install dependencies
run: uv sync --extra dev run: uv sync --all-extras
- name: Run MyPy for graphiti-core - name: Run Pyright for graphiti-core
shell: bash shell: bash
run: | run: |
set -o pipefail uv run pyright ./graphiti_core
uv run mypy ./graphiti_core --show-column-numbers --show-error-codes | sed -E '
s/^(.*):([0-9]+):([0-9]+): (error|warning): (.+) \[(.+)\]/::error file=\1,line=\2,endLine=\2,col=\3,title=\6::\5/;
s/^(.*):([0-9]+):([0-9]+): note: (.+)/::notice file=\1,line=\2,endLine=\2,col=\3,title=Note::\4/;
'
- name: Install graph-service dependencies - name: Install graph-service dependencies
shell: bash shell: bash
run: | run: |
cd server cd server
uv sync --extra dev uv sync --all-extras
- name: Run MyPy for graph-service - name: Run Pyright for graph-service
shell: bash shell: bash
run: | run: |
cd server cd server
set -o pipefail uv run pyright .
uv run mypy . --show-column-numbers --show-error-codes | sed -E '
s/^(.*):([0-9]+):([0-9]+): (error|warning): (.+) \[(.+)\]/::error file=\1,line=\2,endLine=\2,col=\3,title=\6::\5/;
s/^(.*):([0-9]+):([0-9]+): note: (.+)/::notice file=\1,line=\2,endLine=\2,col=\3,title=Note::\4/;
'

View file

@ -30,7 +30,7 @@ jobs:
- name: Install redis-cli for FalkorDB health check - name: Install redis-cli for FalkorDB health check
run: sudo apt-get update && sudo apt-get install -y redis-tools run: sudo apt-get update && sudo apt-get install -y redis-tools
- name: Install dependencies - name: Install dependencies
run: uv sync --extra dev run: uv sync --all-extras
- name: Run non-integration tests - name: Run non-integration tests
env: env:
PYTHONPATH: ${{ github.workspace }} PYTHONPATH: ${{ github.workspace }}

View file

@ -5,7 +5,7 @@ PYTHON = python3
UV = uv UV = uv
PYTEST = $(UV) run pytest PYTEST = $(UV) run pytest
RUFF = $(UV) run ruff RUFF = $(UV) run ruff
MYPY = $(UV) run mypy PYRIGHT = $(UV) run pyright
# Default target # Default target
all: format lint test all: format lint test
@ -22,7 +22,7 @@ format:
# Lint code # Lint code
lint: lint:
$(RUFF) check $(RUFF) check
$(MYPY) ./graphiti_core --show-column-numbers --show-error-codes --pretty $(PYRIGHT) ./graphiti_core
# Run tests # Run tests
test: test:

View file

@ -14,4 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
""" """
__all__ = ['GraphDriver', 'Neo4jDriver', 'FalkorDriver'] from falkordb import FalkorDB
from neo4j import Neo4jDriver
__all__ = ['Neo4jDriver', 'FalkorDB']

View file

@ -51,11 +51,11 @@ class FalkorDriverSession(GraphDriverSession):
if isinstance(query, list): if isinstance(query, list):
for cypher, params in query: for cypher, params in query:
params = convert_datetimes_to_strings(params) params = convert_datetimes_to_strings(params)
await self.graph.query(str(cypher), params) await self.graph.query(str(cypher), params) # type: ignore[reportUnknownArgumentType]
else: else:
params = dict(kwargs) params = dict(kwargs)
params = convert_datetimes_to_strings(params) params = convert_datetimes_to_strings(params)
await self.graph.query(str(query), params) await self.graph.query(str(query), params) # type: ignore[reportUnknownArgumentType]
# Assuming `graph.query` is async (ideal); otherwise, wrap in executor # Assuming `graph.query` is async (ideal); otherwise, wrap in executor
return None return None
@ -99,7 +99,7 @@ class FalkorDriver(GraphDriver):
params = convert_datetimes_to_strings(dict(kwargs)) params = convert_datetimes_to_strings(dict(kwargs))
try: try:
result = await graph.query(cypher_query_, params) result = await graph.query(cypher_query_, params) # type: ignore[reportUnknownArgumentType]
except Exception as e: except Exception as e:
if 'already indexed' in str(e): if 'already indexed' in str(e):
# check if index already exists # check if index already exists

View file

@ -18,7 +18,7 @@ import logging
from collections.abc import Coroutine from collections.abc import Coroutine
from typing import Any from typing import Any
from neo4j import AsyncGraphDatabase from neo4j import AsyncGraphDatabase, EagerResult
from typing_extensions import LiteralString from typing_extensions import LiteralString
from graphiti_core.driver.driver import GraphDriver, GraphDriverSession from graphiti_core.driver.driver import GraphDriver, GraphDriverSession
@ -42,7 +42,7 @@ class Neo4jDriver(GraphDriver):
auth=(user or '', password or ''), auth=(user or '', password or ''),
) )
async def execute_query(self, cypher_query_: LiteralString, **kwargs: Any) -> Coroutine: async def execute_query(self, cypher_query_: LiteralString, **kwargs: Any) -> EagerResult:
params = kwargs.pop('params', None) params = kwargs.pop('params', None)
result = await self.client.execute_query(cypher_query_, parameters_=params, **kwargs) result = await self.client.execute_query(cypher_query_, parameters_=params, **kwargs)
@ -54,7 +54,9 @@ class Neo4jDriver(GraphDriver):
async def close(self) -> None: async def close(self) -> None:
return await self.client.close() return await self.client.close()
def delete_all_indexes(self, database_: str = DEFAULT_DATABASE) -> Coroutine: def delete_all_indexes(
self, database_: str = DEFAULT_DATABASE
) -> Coroutine[Any, Any, EagerResult]:
return self.client.execute_query( return self.client.execute_query(
'CALL db.indexes() YIELD name DROP INDEX name', 'CALL db.indexes() YIELD name DROP INDEX name',
database_=database_, database_=database_,

View file

@ -38,7 +38,7 @@ class VoyageAIEmbedder(EmbedderClient):
if config is None: if config is None:
config = VoyageAIEmbedderConfig() config = VoyageAIEmbedderConfig()
self.config = config self.config = config
self.client = voyageai.AsyncClient(api_key=config.api_key) self.client = voyageai.AsyncClient(api_key=config.api_key) # type: ignore[reportUnknownMemberType]
async def create( async def create(
self, input_data: str | list[str] | Iterable[int] | Iterable[Iterable[int]] self, input_data: str | list[str] | Iterable[int] | Iterable[Iterable[int]]

View file

@ -19,6 +19,7 @@ import os
import re import re
from collections.abc import Coroutine from collections.abc import Coroutine
from datetime import datetime from datetime import datetime
from typing import Any
import numpy as np import numpy as np
from dotenv import load_dotenv from dotenv import load_dotenv
@ -99,7 +100,7 @@ def normalize_l2(embedding: list[float]) -> NDArray:
async def semaphore_gather( async def semaphore_gather(
*coroutines: Coroutine, *coroutines: Coroutine,
max_coroutines: int | None = None, max_coroutines: int | None = None,
): ) -> list[Any]:
semaphore = asyncio.Semaphore(max_coroutines or SEMAPHORE_LIMIT) semaphore = asyncio.Semaphore(max_coroutines or SEMAPHORE_LIMIT)
async def _wrap_coroutine(coroutine): async def _wrap_coroutine(coroutine):

View file

@ -19,7 +19,6 @@ from enum import Enum
from typing import Any from typing import Any
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing_extensions import LiteralString
class ComparisonOperator(Enum): class ComparisonOperator(Enum):
@ -53,8 +52,8 @@ class SearchFilters(BaseModel):
def node_search_filter_query_constructor( def node_search_filter_query_constructor(
filters: SearchFilters, filters: SearchFilters,
) -> tuple[LiteralString, dict[str, Any]]: ) -> tuple[str, dict[str, Any]]:
filter_query: LiteralString = '' filter_query: str = ''
filter_params: dict[str, Any] = {} filter_params: dict[str, Any] = {}
if filters.node_labels is not None: if filters.node_labels is not None:
@ -67,8 +66,8 @@ def node_search_filter_query_constructor(
def edge_search_filter_query_constructor( def edge_search_filter_query_constructor(
filters: SearchFilters, filters: SearchFilters,
) -> tuple[LiteralString, dict[str, Any]]: ) -> tuple[str, dict[str, Any]]:
filter_query: LiteralString = '' filter_query: str = ''
filter_params: dict[str, Any] = {} filter_params: dict[str, Any] = {}
if filters.edge_types is not None: if filters.edge_types is not None:

View file

@ -40,7 +40,7 @@ async def get_community_clusters(
database_=DEFAULT_DATABASE, database_=DEFAULT_DATABASE,
) )
group_ids = group_id_values[0]['group_ids'] group_ids = group_id_values[0]['group_ids'] if group_id_values else []
for group_id in group_ids: for group_id in group_ids:
projection: dict[str, list[Neighbor]] = {} projection: dict[str, list[Neighbor]] = {}

View file

@ -297,7 +297,7 @@ async def resolve_extracted_edges(
embedder = clients.embedder embedder = clients.embedder
await create_entity_edge_embeddings(embedder, extracted_edges) await create_entity_edge_embeddings(embedder, extracted_edges)
search_results: tuple[list[list[EntityEdge]], list[list[EntityEdge]]] = await semaphore_gather( search_results = await semaphore_gather(
get_relevant_edges(driver, extracted_edges, SearchFilters()), get_relevant_edges(driver, extracted_edges, SearchFilters()),
get_edge_invalidation_candidates(driver, extracted_edges, SearchFilters(), 0.2), get_edge_invalidation_candidates(driver, extracted_edges, SearchFilters(), 0.2),
) )

View file

@ -31,7 +31,7 @@ groq = ["groq>=0.2.0"]
google-genai = ["google-genai>=1.8.0"] google-genai = ["google-genai>=1.8.0"]
falkord-db = ["falkordb>=1.1.2,<2.0.0"] falkord-db = ["falkordb>=1.1.2,<2.0.0"]
dev = [ dev = [
"mypy>=1.11.1", "pyright>=1.1.380",
"groq>=0.2.0", "groq>=0.2.0",
"anthropic>=0.49.0", "anthropic>=0.49.0",
"google-genai>=1.8.0", "google-genai>=1.8.0",
@ -83,13 +83,11 @@ quote-style = "single"
indent-style = "space" indent-style = "space"
docstring-code-format = true docstring-code-format = true
[tool.mypy] [tool.pyright]
packages = ["graphiti_core"] include = ["graphiti_core"]
pythonVersion = "3.10"
typeCheckingMode = "basic"
[[tool.mypy.overrides]] [[tool.pyright.overrides]]
module = "falkordb" include = ["**/falkordb*"]
ignore_missing_imports = true reportMissingImports = false
[[tool.mypy.overrides]]
module = "falkordb.asyncio"
ignore_missing_imports = true

View file

@ -5,7 +5,7 @@ PYTHON = python3
UV = uv UV = uv
PYTEST = $(UV) run pytest PYTEST = $(UV) run pytest
RUFF = $(UV) run ruff RUFF = $(UV) run ruff
MYPY = $(UV) run mypy PYRIGHT = $(UV) run pyright
# Default target # Default target
all: format lint test all: format lint test
@ -22,7 +22,7 @@ format:
# Lint code # Lint code
lint: lint:
$(RUFF) check $(RUFF) check
$(MYPY) . --show-column-numbers --show-error-codes --pretty $(PYRIGHT) .
# Run tests # Run tests
test: test:

View file

@ -20,7 +20,7 @@ class Settings(BaseSettings):
@lru_cache @lru_cache
def get_settings(): def get_settings():
return Settings() return Settings() # type: ignore[call-arg]
ZepEnvDep = Annotated[Settings, Depends(get_settings)] ZepEnvDep = Annotated[Settings, Depends(get_settings)]

View file

@ -18,7 +18,7 @@ dependencies = [
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"pydantic>=2.8.2", "pydantic>=2.8.2",
"mypy>=1.11.1", "pyright>=1.1.380",
"pytest>=8.3.2", "pytest>=8.3.2",
"python-dotenv>=1.0.1", "python-dotenv>=1.0.1",
"pytest-asyncio>=0.24.0", "pytest-asyncio>=0.24.0",
@ -61,3 +61,8 @@ ignore = ["E501"]
quote-style = "single" quote-style = "single"
indent-style = "space" indent-style = "space"
docstring-code-format = true docstring-code-format = true
[tool.pyright]
include = ["."]
pythonVersion = "3.10"
typeCheckingMode = "standard"

83
server/uv.lock generated
View file

@ -137,8 +137,8 @@ dependencies = [
[package.optional-dependencies] [package.optional-dependencies]
dev = [ dev = [
{ name = "fastapi-cli" }, { name = "fastapi-cli" },
{ name = "mypy" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pyright" },
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-asyncio" }, { name = "pytest-asyncio" },
{ name = "pytest-xdist" }, { name = "pytest-xdist" },
@ -152,9 +152,9 @@ requires-dist = [
{ name = "fastapi-cli", marker = "extra == 'dev'", specifier = ">=0.0.5" }, { name = "fastapi-cli", marker = "extra == 'dev'", specifier = ">=0.0.5" },
{ name = "graphiti-core" }, { name = "graphiti-core" },
{ name = "httpx", specifier = ">=0.28.1" }, { name = "httpx", specifier = ">=0.28.1" },
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.11.1" },
{ name = "pydantic", marker = "extra == 'dev'", specifier = ">=2.8.2" }, { name = "pydantic", marker = "extra == 'dev'", specifier = ">=2.8.2" },
{ name = "pydantic-settings", specifier = ">=2.4.0" }, { name = "pydantic-settings", specifier = ">=2.4.0" },
{ name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.380" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.2" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.2" },
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" },
{ name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.6.1" }, { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.6.1" },
@ -365,54 +365,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
] ]
[[package]]
name = "mypy"
version = "1.16.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644 },
{ url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033 },
{ url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645 },
{ url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986 },
{ url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632 },
{ url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391 },
{ url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557 },
{ url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921 },
{ url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887 },
{ url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658 },
{ url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486 },
{ url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482 },
{ url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493 },
{ url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687 },
{ url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723 },
{ url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980 },
{ url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328 },
{ url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321 },
{ url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480 },
{ url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538 },
{ url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839 },
{ url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634 },
{ url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584 },
{ url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886 },
{ url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923 },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
]
[[package]] [[package]]
name = "neo4j" name = "neo4j"
version = "5.28.1" version = "5.28.1"
@ -425,6 +377,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/57/94225fe5e9dabdc0ff60c88cbfcedf11277f4b34e7ab1373d3e62dbdd207/neo4j-5.28.1-py3-none-any.whl", hash = "sha256:6755ef9e5f4e14b403aef1138fb6315b120631a0075c138b5ddb2a06b87b09fd", size = 312258 }, { url = "https://files.pythonhosted.org/packages/6a/57/94225fe5e9dabdc0ff60c88cbfcedf11277f4b34e7ab1373d3e62dbdd207/neo4j-5.28.1-py3-none-any.whl", hash = "sha256:6755ef9e5f4e14b403aef1138fb6315b120631a0075c138b5ddb2a06b87b09fd", size = 312258 },
] ]
[[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]] [[package]]
name = "numpy" name = "numpy"
version = "2.2.6" version = "2.2.6"
@ -515,15 +476,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
] ]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.6.0" version = "1.6.0"
@ -658,6 +610,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 },
] ]
[[package]]
name = "pyright"
version = "1.1.402"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nodeenv" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/aa/04/ce0c132d00e20f2d2fb3b3e7c125264ca8b909e693841210534b1ea1752f/pyright-1.1.402.tar.gz", hash = "sha256:85a33c2d40cd4439c66aa946fd4ce71ab2f3f5b8c22ce36a623f59ac22937683", size = 3888207 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/37/1a1c62d955e82adae588be8e374c7f77b165b6cb4203f7d581269959abbc/pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982", size = 5624004 },
]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "8.4.1" version = "8.4.1"

View file

@ -38,7 +38,7 @@ NEO4J_PASSWORD = os.getenv('NEO4J_PASSWORD')
# Test entity type definitions # Test entity type definitions
class Person(BaseModel): class Person(BaseModel):
"""A human person mentioned in the conversation.""" """A human person mentioned in the conversation."""
first_name: str | None = Field(None, description='First name of the person') first_name: str | None = Field(None, description='First name of the person')
last_name: str | None = Field(None, description='Last name of the person') last_name: str | None = Field(None, description='Last name of the person')
occupation: str | None = Field(None, description='Job or profession of the person') occupation: str | None = Field(None, description='Job or profession of the person')
@ -46,15 +46,21 @@ class Person(BaseModel):
class Organization(BaseModel): class Organization(BaseModel):
"""A company, institution, or organized group.""" """A company, institution, or organized group."""
organization_type: str | None = Field(None, description='Type of organization (company, NGO, etc.)') organization_type: str | None = Field(
industry: str | None = Field(None, description='Industry or sector the organization operates in') None, description='Type of organization (company, NGO, etc.)'
)
industry: str | None = Field(
None, description='Industry or sector the organization operates in'
)
class Location(BaseModel): class Location(BaseModel):
"""A geographic location, place, or address.""" """A geographic location, place, or address."""
location_type: str | None = Field(None, description='Type of location (city, country, building, etc.)') location_type: str | None = Field(
None, description='Type of location (city, country, building, etc.)'
)
coordinates: str | None = Field(None, description='Geographic coordinates if available') coordinates: str | None = Field(None, description='Geographic coordinates if available')
@ -62,49 +68,51 @@ class Location(BaseModel):
async def test_exclude_default_entity_type(): async def test_exclude_default_entity_type():
"""Test excluding the default 'Entity' type while keeping custom types.""" """Test excluding the default 'Entity' type while keeping custom types."""
graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD) graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD)
try: try:
await graphiti.build_indices_and_constraints() await graphiti.build_indices_and_constraints()
# Define entity types but exclude the default 'Entity' type # Define entity types but exclude the default 'Entity' type
entity_types = { entity_types = {
'Person': Person, 'Person': Person,
'Organization': Organization, 'Organization': Organization,
} }
# Add an episode that would normally create both Entity and custom type entities # Add an episode that would normally create both Entity and custom type entities
episode_content = "John Smith works at Acme Corporation in New York. The weather is nice today." episode_content = (
'John Smith works at Acme Corporation in New York. The weather is nice today.'
)
result = await graphiti.add_episode( result = await graphiti.add_episode(
name="Business Meeting", name='Business Meeting',
episode_body=episode_content, episode_body=episode_content,
source_description="Meeting notes", source_description='Meeting notes',
reference_time=datetime.now(timezone.utc), reference_time=datetime.now(timezone.utc),
entity_types=entity_types, entity_types=entity_types,
excluded_entity_types=['Entity'], # Exclude default type excluded_entity_types=['Entity'], # Exclude default type
group_id='test_exclude_default' group_id='test_exclude_default',
) )
# Verify that nodes were created (custom types should still work) # Verify that nodes were created (custom types should still work)
assert result is not None assert result is not None
# Search for nodes to verify only custom types were created # Search for nodes to verify only custom types were created
search_results = await graphiti.search_( search_results = await graphiti.search_(
query="John Smith Acme Corporation", query='John Smith Acme Corporation', group_ids=['test_exclude_default']
group_ids=['test_exclude_default']
) )
# Check that entities were created but with specific types, not default 'Entity' # Check that entities were created but with specific types, not default 'Entity'
found_nodes = search_results.nodes found_nodes = search_results.nodes
for node in found_nodes: for node in found_nodes:
assert 'Entity' in node.labels # All nodes should have Entity label assert 'Entity' in node.labels # All nodes should have Entity label
# But they should also have specific type labels # But they should also have specific type labels
assert any(label in ['Person', 'Organization'] for label in node.labels), \ assert any(label in ['Person', 'Organization'] for label in node.labels), (
f"Node {node.name} should have a specific type label, got: {node.labels}" f'Node {node.name} should have a specific type label, got: {node.labels}'
)
# Clean up # Clean up
await _cleanup_test_nodes(graphiti, 'test_exclude_default') await _cleanup_test_nodes(graphiti, 'test_exclude_default')
finally: finally:
await graphiti.close() await graphiti.close()
@ -113,54 +121,57 @@ async def test_exclude_default_entity_type():
async def test_exclude_specific_custom_types(): async def test_exclude_specific_custom_types():
"""Test excluding specific custom entity types while keeping others.""" """Test excluding specific custom entity types while keeping others."""
graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD) graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD)
try: try:
await graphiti.build_indices_and_constraints() await graphiti.build_indices_and_constraints()
# Define multiple entity types # Define multiple entity types
entity_types = { entity_types = {
'Person': Person, 'Person': Person,
'Organization': Organization, 'Organization': Organization,
'Location': Location, 'Location': Location,
} }
# Add an episode with content that would create all types # Add an episode with content that would create all types
episode_content = "Sarah Johnson from Google visited the San Francisco office to discuss the new project." episode_content = (
'Sarah Johnson from Google visited the San Francisco office to discuss the new project.'
)
result = await graphiti.add_episode( result = await graphiti.add_episode(
name="Office Visit", name='Office Visit',
episode_body=episode_content, episode_body=episode_content,
source_description="Visit report", source_description='Visit report',
reference_time=datetime.now(timezone.utc), reference_time=datetime.now(timezone.utc),
entity_types=entity_types, entity_types=entity_types,
excluded_entity_types=['Organization', 'Location'], # Exclude these types excluded_entity_types=['Organization', 'Location'], # Exclude these types
group_id='test_exclude_custom' group_id='test_exclude_custom',
) )
assert result is not None assert result is not None
# Search for nodes to verify only Person and Entity types were created # Search for nodes to verify only Person and Entity types were created
search_results = await graphiti.search_( search_results = await graphiti.search_(
query="Sarah Johnson Google San Francisco", query='Sarah Johnson Google San Francisco', group_ids=['test_exclude_custom']
group_ids=['test_exclude_custom']
) )
found_nodes = search_results.nodes found_nodes = search_results.nodes
# Should have Person and Entity type nodes, but no Organization or Location # Should have Person and Entity type nodes, but no Organization or Location
for node in found_nodes: for node in found_nodes:
assert 'Entity' in node.labels assert 'Entity' in node.labels
# Should not have excluded types # Should not have excluded types
assert 'Organization' not in node.labels, f"Found excluded Organization in node: {node.name}" assert 'Organization' not in node.labels, (
assert 'Location' not in node.labels, f"Found excluded Location in node: {node.name}" f'Found excluded Organization in node: {node.name}'
)
assert 'Location' not in node.labels, f'Found excluded Location in node: {node.name}'
# Should find at least one Person entity (Sarah Johnson) # Should find at least one Person entity (Sarah Johnson)
person_nodes = [n for n in found_nodes if 'Person' in n.labels] person_nodes = [n for n in found_nodes if 'Person' in n.labels]
assert len(person_nodes) > 0, "Should have found at least one Person entity" assert len(person_nodes) > 0, 'Should have found at least one Person entity'
# Clean up # Clean up
await _cleanup_test_nodes(graphiti, 'test_exclude_custom') await _cleanup_test_nodes(graphiti, 'test_exclude_custom')
finally: finally:
await graphiti.close() await graphiti.close()
@ -169,41 +180,42 @@ async def test_exclude_specific_custom_types():
async def test_exclude_all_types(): async def test_exclude_all_types():
"""Test excluding all entity types (edge case).""" """Test excluding all entity types (edge case)."""
graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD) graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD)
try: try:
await graphiti.build_indices_and_constraints() await graphiti.build_indices_and_constraints()
entity_types = { entity_types = {
'Person': Person, 'Person': Person,
'Organization': Organization, 'Organization': Organization,
} }
# Exclude all types # Exclude all types
result = await graphiti.add_episode( result = await graphiti.add_episode(
name="No Entities", name='No Entities',
episode_body="This text mentions John and Microsoft but no entities should be created.", episode_body='This text mentions John and Microsoft but no entities should be created.',
source_description="Test content", source_description='Test content',
reference_time=datetime.now(timezone.utc), reference_time=datetime.now(timezone.utc),
entity_types=entity_types, entity_types=entity_types,
excluded_entity_types=['Entity', 'Person', 'Organization'], # Exclude everything excluded_entity_types=['Entity', 'Person', 'Organization'], # Exclude everything
group_id='test_exclude_all' group_id='test_exclude_all',
) )
assert result is not None assert result is not None
# Search for nodes - should find very few or none from this episode # Search for nodes - should find very few or none from this episode
search_results = await graphiti.search_( search_results = await graphiti.search_(
query="John Microsoft", query='John Microsoft', group_ids=['test_exclude_all']
group_ids=['test_exclude_all']
) )
# There should be minimal to no entities created # There should be minimal to no entities created
found_nodes = search_results.nodes found_nodes = search_results.nodes
assert len(found_nodes) == 0, f"Expected no entities, but found: {[n.name for n in found_nodes]}" assert len(found_nodes) == 0, (
f'Expected no entities, but found: {[n.name for n in found_nodes]}'
)
# Clean up # Clean up
await _cleanup_test_nodes(graphiti, 'test_exclude_all') await _cleanup_test_nodes(graphiti, 'test_exclude_all')
finally: finally:
await graphiti.close() await graphiti.close()
@ -212,47 +224,46 @@ async def test_exclude_all_types():
async def test_exclude_no_types(): async def test_exclude_no_types():
"""Test normal behavior when no types are excluded (baseline test).""" """Test normal behavior when no types are excluded (baseline test)."""
graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD) graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD)
try: try:
await graphiti.build_indices_and_constraints() await graphiti.build_indices_and_constraints()
entity_types = { entity_types = {
'Person': Person, 'Person': Person,
'Organization': Organization, 'Organization': Organization,
} }
# Don't exclude any types # Don't exclude any types
result = await graphiti.add_episode( result = await graphiti.add_episode(
name="Normal Behavior", name='Normal Behavior',
episode_body="Alice Smith works at TechCorp.", episode_body='Alice Smith works at TechCorp.',
source_description="Normal test", source_description='Normal test',
reference_time=datetime.now(timezone.utc), reference_time=datetime.now(timezone.utc),
entity_types=entity_types, entity_types=entity_types,
excluded_entity_types=None, # No exclusions excluded_entity_types=None, # No exclusions
group_id='test_exclude_none' group_id='test_exclude_none',
) )
assert result is not None assert result is not None
# Search for nodes - should find entities of all types # Search for nodes - should find entities of all types
search_results = await graphiti.search_( search_results = await graphiti.search_(
query="Alice Smith TechCorp", query='Alice Smith TechCorp', group_ids=['test_exclude_none']
group_ids=['test_exclude_none']
) )
found_nodes = search_results.nodes found_nodes = search_results.nodes
assert len(found_nodes) > 0, "Should have found some entities" assert len(found_nodes) > 0, 'Should have found some entities'
# Should have both Person and Organization entities # Should have both Person and Organization entities
person_nodes = [n for n in found_nodes if 'Person' in n.labels] person_nodes = [n for n in found_nodes if 'Person' in n.labels]
org_nodes = [n for n in found_nodes if 'Organization' in n.labels] org_nodes = [n for n in found_nodes if 'Organization' in n.labels]
assert len(person_nodes) > 0, "Should have found Person entities" assert len(person_nodes) > 0, 'Should have found Person entities'
assert len(org_nodes) > 0, "Should have found Organization entities" assert len(org_nodes) > 0, 'Should have found Organization entities'
# Clean up # Clean up
await _cleanup_test_nodes(graphiti, 'test_exclude_none') await _cleanup_test_nodes(graphiti, 'test_exclude_none')
finally: finally:
await graphiti.close() await graphiti.close()
@ -263,7 +274,7 @@ def test_validation_valid_excluded_types():
'Person': Person, 'Person': Person,
'Organization': Organization, 'Organization': Organization,
} }
# Valid exclusions # Valid exclusions
assert validate_excluded_entity_types(['Entity'], entity_types) is True assert validate_excluded_entity_types(['Entity'], entity_types) is True
assert validate_excluded_entity_types(['Person'], entity_types) is True assert validate_excluded_entity_types(['Person'], entity_types) is True
@ -278,12 +289,12 @@ def test_validation_invalid_excluded_types():
'Person': Person, 'Person': Person,
'Organization': Organization, 'Organization': Organization,
} }
# Invalid exclusions should raise ValueError # Invalid exclusions should raise ValueError
with pytest.raises(ValueError, match="Invalid excluded entity types"): with pytest.raises(ValueError, match='Invalid excluded entity types'):
validate_excluded_entity_types(['InvalidType'], entity_types) validate_excluded_entity_types(['InvalidType'], entity_types)
with pytest.raises(ValueError, match="Invalid excluded entity types"): with pytest.raises(ValueError, match='Invalid excluded entity types'):
validate_excluded_entity_types(['Person', 'NonExistentType'], entity_types) validate_excluded_entity_types(['Person', 'NonExistentType'], entity_types)
@ -291,24 +302,24 @@ def test_validation_invalid_excluded_types():
async def test_excluded_types_parameter_validation_in_add_episode(): async def test_excluded_types_parameter_validation_in_add_episode():
"""Test that add_episode validates excluded_entity_types parameter.""" """Test that add_episode validates excluded_entity_types parameter."""
graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD) graphiti = Graphiti(NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD)
try: try:
entity_types = { entity_types = {
'Person': Person, 'Person': Person,
} }
# Should raise ValueError for invalid excluded type # Should raise ValueError for invalid excluded type
with pytest.raises(ValueError, match="Invalid excluded entity types"): with pytest.raises(ValueError, match='Invalid excluded entity types'):
await graphiti.add_episode( await graphiti.add_episode(
name="Invalid Test", name='Invalid Test',
episode_body="Test content", episode_body='Test content',
source_description="Test", source_description='Test',
reference_time=datetime.now(timezone.utc), reference_time=datetime.now(timezone.utc),
entity_types=entity_types, entity_types=entity_types,
excluded_entity_types=['NonExistentType'], excluded_entity_types=['NonExistentType'],
group_id='test_validation' group_id='test_validation',
) )
finally: finally:
await graphiti.close() await graphiti.close()
@ -317,15 +328,12 @@ async def _cleanup_test_nodes(graphiti: Graphiti, group_id: str):
"""Helper function to clean up test nodes.""" """Helper function to clean up test nodes."""
try: try:
# Get all nodes for this group # Get all nodes for this group
search_results = await graphiti.search_( search_results = await graphiti.search_(query='*', group_ids=[group_id])
query="*",
group_ids=[group_id]
)
# Delete all found nodes # Delete all found nodes
for node in search_results.nodes: for node in search_results.nodes:
await node.delete(graphiti.driver) await node.delete(graphiti.driver)
except Exception as e: except Exception as e:
# Log but don't fail the test if cleanup fails # Log but don't fail the test if cleanup fails
print(f"Warning: Failed to clean up test nodes for group {group_id}: {e}") print(f'Warning: Failed to clean up test nodes for group {group_id}: {e}')

115
uv.lock generated
View file

@ -266,6 +266,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 },
] ]
[[package]]
name = "backoff"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 },
]
[[package]] [[package]]
name = "beautifulsoup4" name = "beautifulsoup4"
version = "4.13.4" version = "4.13.4"
@ -531,7 +540,7 @@ name = "exceptiongroup"
version = "1.3.0" version = "1.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 }
wheels = [ wheels = [
@ -729,13 +738,14 @@ wheels = [
[[package]] [[package]]
name = "graphiti-core" name = "graphiti-core"
version = "0.13.2" version = "0.14.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "diskcache" }, { name = "diskcache" },
{ name = "neo4j" }, { name = "neo4j" },
{ name = "numpy" }, { name = "numpy" },
{ name = "openai" }, { name = "openai" },
{ name = "posthog" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "tenacity" }, { name = "tenacity" },
@ -756,7 +766,7 @@ dev = [
{ name = "langchain-openai" }, { name = "langchain-openai" },
{ name = "langgraph" }, { name = "langgraph" },
{ name = "langsmith" }, { name = "langsmith" },
{ name = "mypy" }, { name = "pyright" },
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-asyncio" }, { name = "pytest-asyncio" },
{ name = "pytest-xdist" }, { name = "pytest-xdist" },
@ -792,11 +802,12 @@ requires-dist = [
{ name = "langchain-openai", marker = "extra == 'dev'", specifier = ">=0.2.6" }, { name = "langchain-openai", marker = "extra == 'dev'", specifier = ">=0.2.6" },
{ name = "langgraph", marker = "extra == 'dev'", specifier = ">=0.2.15" }, { name = "langgraph", marker = "extra == 'dev'", specifier = ">=0.2.15" },
{ name = "langsmith", marker = "extra == 'dev'", specifier = ">=0.1.108" }, { name = "langsmith", marker = "extra == 'dev'", specifier = ">=0.1.108" },
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.11.1" },
{ name = "neo4j", specifier = ">=5.26.0" }, { name = "neo4j", specifier = ">=5.26.0" },
{ name = "numpy", specifier = ">=1.0.0" }, { name = "numpy", specifier = ">=1.0.0" },
{ name = "openai", specifier = ">=1.91.0" }, { name = "openai", specifier = ">=1.91.0" },
{ name = "posthog", specifier = ">=3.0.0" },
{ name = "pydantic", specifier = ">=2.11.5" }, { name = "pydantic", specifier = ">=2.11.5" },
{ name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.380" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.3" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.3" },
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" },
{ name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.6.1" }, { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.6.1" },
@ -1605,54 +1616,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/07/9f/d4719ce55a1d8bf6619e8bb92f1e2e7399026ea85ae0c324ec77ee06c050/multidict-6.5.1-py3-none-any.whl", hash = "sha256:895354f4a38f53a1df2cc3fa2223fa714cff2b079a9f018a76cad35e7f0f044c", size = 12185 }, { url = "https://files.pythonhosted.org/packages/07/9f/d4719ce55a1d8bf6619e8bb92f1e2e7399026ea85ae0c324ec77ee06c050/multidict-6.5.1-py3-none-any.whl", hash = "sha256:895354f4a38f53a1df2cc3fa2223fa714cff2b079a9f018a76cad35e7f0f044c", size = 12185 },
] ]
[[package]]
name = "mypy"
version = "1.16.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644 },
{ url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033 },
{ url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645 },
{ url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986 },
{ url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632 },
{ url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391 },
{ url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557 },
{ url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921 },
{ url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887 },
{ url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658 },
{ url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486 },
{ url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482 },
{ url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493 },
{ url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687 },
{ url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723 },
{ url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980 },
{ url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328 },
{ url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321 },
{ url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480 },
{ url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538 },
{ url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839 },
{ url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634 },
{ url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584 },
{ url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886 },
{ url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923 },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
]
[[package]] [[package]]
name = "nbclient" name = "nbclient"
version = "0.10.2" version = "0.10.2"
@ -1738,6 +1701,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 },
] ]
[[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]] [[package]]
name = "notebook-shim" name = "notebook-shim"
version = "0.2.4" version = "0.2.4"
@ -2127,15 +2099,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 },
] ]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
]
[[package]] [[package]]
name = "pexpect" name = "pexpect"
version = "4.9.0" version = "4.9.0"
@ -2243,6 +2206,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
] ]
[[package]]
name = "posthog"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "backoff" },
{ name = "distro" },
{ name = "python-dateutil" },
{ name = "requests" },
{ name = "six" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f3/c3/c83883af8cc5e3b45d1bee85edce546a4db369fb8dc8eb6339fad764178b/posthog-6.0.0.tar.gz", hash = "sha256:b7bfa0da03bd9240891885d3e44b747e62192ac9ee6da280f45320f4ad3479e0", size = 88066 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/ec/7a44533c9fe7046ffcfe48ca0e7472ada2633854f474be633f4afed7b044/posthog-6.0.0-py3-none-any.whl", hash = "sha256:01f5d11046a6267d4384f552e819f0f4a7dc885eb19f606c36d44d662df9ff89", size = 104945 },
]
[[package]] [[package]]
name = "prometheus-client" name = "prometheus-client"
version = "0.22.1" version = "0.22.1"
@ -2536,6 +2516,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 }, { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 },
] ]
[[package]]
name = "pyright"
version = "1.1.402"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nodeenv" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/aa/04/ce0c132d00e20f2d2fb3b3e7c125264ca8b909e693841210534b1ea1752f/pyright-1.1.402.tar.gz", hash = "sha256:85a33c2d40cd4439c66aa946fd4ce71ab2f3f5b8c22ce36a623f59ac22937683", size = 3888207 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/37/1a1c62d955e82adae588be8e374c7f77b165b6cb4203f7d581269959abbc/pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982", size = 5624004 },
]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "8.4.1" version = "8.4.1"