kodit 0.1.10__tar.gz → 0.1.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kodit might be problematic. Click here for more details.

Files changed (86) hide show
  1. {kodit-0.1.10 → kodit-0.1.11}/.gitignore +3 -1
  2. {kodit-0.1.10 → kodit-0.1.11}/PKG-INFO +1 -1
  3. {kodit-0.1.10 → kodit-0.1.11}/pyproject.toml +1 -0
  4. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/_version.py +2 -2
  5. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/retreival/repository.py +99 -51
  6. kodit-0.1.11/tests/performance/similarity.py +139 -0
  7. {kodit-0.1.10 → kodit-0.1.11}/uv.lock +14 -0
  8. {kodit-0.1.10 → kodit-0.1.11}/.cursor/rules/kodit.mdc +0 -0
  9. {kodit-0.1.10 → kodit-0.1.11}/.github/CODE_OF_CONDUCT.md +0 -0
  10. {kodit-0.1.10 → kodit-0.1.11}/.github/CONTRIBUTING.md +0 -0
  11. {kodit-0.1.10 → kodit-0.1.11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {kodit-0.1.10 → kodit-0.1.11}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  13. {kodit-0.1.10 → kodit-0.1.11}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  14. {kodit-0.1.10 → kodit-0.1.11}/.github/workflows/docker.yaml +0 -0
  15. {kodit-0.1.10 → kodit-0.1.11}/.github/workflows/docs.yaml +0 -0
  16. {kodit-0.1.10 → kodit-0.1.11}/.github/workflows/pypi-test.yaml +0 -0
  17. {kodit-0.1.10 → kodit-0.1.11}/.github/workflows/pypi.yaml +0 -0
  18. {kodit-0.1.10 → kodit-0.1.11}/.github/workflows/test.yaml +0 -0
  19. {kodit-0.1.10 → kodit-0.1.11}/.python-version +0 -0
  20. {kodit-0.1.10 → kodit-0.1.11}/.vscode/launch.json +0 -0
  21. {kodit-0.1.10 → kodit-0.1.11}/.vscode/settings.json +0 -0
  22. {kodit-0.1.10 → kodit-0.1.11}/Dockerfile +0 -0
  23. {kodit-0.1.10 → kodit-0.1.11}/LICENSE +0 -0
  24. {kodit-0.1.10 → kodit-0.1.11}/README.md +0 -0
  25. {kodit-0.1.10 → kodit-0.1.11}/alembic.ini +0 -0
  26. {kodit-0.1.10 → kodit-0.1.11}/docs/_index.md +0 -0
  27. {kodit-0.1.10 → kodit-0.1.11}/docs/developer/index.md +0 -0
  28. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/.gitignore +0 -0
  29. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/__init__.py +0 -0
  30. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/app.py +0 -0
  31. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/bm25/__init__.py +0 -0
  32. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/bm25/bm25.py +0 -0
  33. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/cli.py +0 -0
  34. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/config.py +0 -0
  35. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/database.py +0 -0
  36. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/embedding/__init__.py +0 -0
  37. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/embedding/embedding.py +0 -0
  38. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/embedding/models.py +0 -0
  39. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/indexing/__init__.py +0 -0
  40. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/indexing/models.py +0 -0
  41. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/indexing/repository.py +0 -0
  42. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/indexing/service.py +0 -0
  43. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/log.py +0 -0
  44. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/mcp.py +0 -0
  45. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/middleware.py +0 -0
  46. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/migrations/README +0 -0
  47. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/migrations/__init__.py +0 -0
  48. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/migrations/env.py +0 -0
  49. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/migrations/script.py.mako +0 -0
  50. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/migrations/versions/7c3bbc2ab32b_add_embeddings_table.py +0 -0
  51. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/migrations/versions/85155663351e_initial.py +0 -0
  52. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/migrations/versions/__init__.py +0 -0
  53. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/retreival/__init__.py +0 -0
  54. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/retreival/service.py +0 -0
  55. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/snippets/__init__.py +0 -0
  56. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/snippets/languages/__init__.py +0 -0
  57. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/snippets/languages/csharp.scm +0 -0
  58. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/snippets/languages/python.scm +0 -0
  59. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/snippets/method_snippets.py +0 -0
  60. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/snippets/snippets.py +0 -0
  61. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/sources/__init__.py +0 -0
  62. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/sources/models.py +0 -0
  63. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/sources/repository.py +0 -0
  64. {kodit-0.1.10 → kodit-0.1.11}/src/kodit/sources/service.py +0 -0
  65. {kodit-0.1.10 → kodit-0.1.11}/tests/__init__.py +0 -0
  66. {kodit-0.1.10 → kodit-0.1.11}/tests/conftest.py +0 -0
  67. {kodit-0.1.10 → kodit-0.1.11}/tests/experiments/embedding.py +0 -0
  68. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/__init__.py +0 -0
  69. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/cli_test.py +0 -0
  70. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/e2e.py +0 -0
  71. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/embedding/__init__.py +0 -0
  72. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/embedding/embedding_test.py +0 -0
  73. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/indexing/__init__.py +0 -0
  74. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/indexing/test_service.py +0 -0
  75. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/mcp_test.py +0 -0
  76. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/retreival/__init__.py +0 -0
  77. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/retreival/repository_test.py +0 -0
  78. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/retreival/test_service.py +0 -0
  79. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/snippets/__init__.py +0 -0
  80. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/snippets/csharp.cs +0 -0
  81. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/snippets/detect_language_test.py +0 -0
  82. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/snippets/method_extraction_test.py +0 -0
  83. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/snippets/python.py +0 -0
  84. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/sources/__init__.py +0 -0
  85. {kodit-0.1.10 → kodit-0.1.11}/tests/kodit/sources/test_service.py +0 -0
  86. {kodit-0.1.10 → kodit-0.1.11}/tests/smoke.sh +0 -0
@@ -174,4 +174,6 @@ cython_debug/
174
174
  .pypirc
175
175
  .kodit/
176
176
  .DS_Store
177
- .kodit.db
177
+ .kodit.db
178
+ benchmark.db
179
+ profile.prof
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kodit
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: Code indexing for better AI code generation
5
5
  Project-URL: Homepage, https://docs.helixml.tech/kodit/
6
6
  Project-URL: Documentation, https://docs.helixml.tech/kodit/
@@ -56,6 +56,7 @@ dev = [
56
56
  "pytest>=8.3.5",
57
57
  "pytest-cov>=6.1.1",
58
58
  "ruff>=0.11.8",
59
+ "snakeviz>=2.2.2",
59
60
  ]
60
61
 
61
62
  [project.urls]
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.10'
21
- __version_tuple__ = version_tuple = (0, 1, 10)
20
+ __version__ = version = '0.1.11'
21
+ __version_tuple__ = version_tuple = (0, 1, 11)
@@ -5,21 +5,14 @@ related to searching and retrieving code snippets, including string-based search
5
5
  and their associated file information.
6
6
  """
7
7
 
8
- import math
9
- from typing import Any, TypeVar
8
+ from typing import TypeVar
10
9
 
10
+ import numpy as np
11
11
  import pydantic
12
12
  from sqlalchemy import (
13
- ColumnElement,
14
- Float,
15
- cast,
16
- desc,
17
- func,
18
- literal,
19
13
  select,
20
14
  )
21
15
  from sqlalchemy.ext.asyncio import AsyncSession
22
- from sqlalchemy.orm import Mapped
23
16
 
24
17
  from kodit.embedding.models import Embedding, EmbeddingType
25
18
  from kodit.indexing.models import Snippet
@@ -129,55 +122,110 @@ class RetrievalRepository:
129
122
  # Return results in the same order as input IDs
130
123
  return [id_to_result[i] for i in ids]
131
124
 
132
- async def list_semantic_results(
133
- self, embedding_type: EmbeddingType, embedding: list[float], top_k: int = 10
134
- ) -> list[tuple[int, float]]:
135
- """List semantic results."""
136
- cosine_similarity = cosine_similarity_json(Embedding.embedding, embedding)
125
+ async def fetch_embeddings(
126
+ self, embedding_type: EmbeddingType
127
+ ) -> list[tuple[int, list[float]]]:
128
+ """Fetch all embeddings of a given type from the database.
137
129
 
138
- query = (
139
- select(Embedding, cosine_similarity)
140
- .where(Embedding.type == embedding_type)
141
- .order_by(desc(cosine_similarity))
142
- .limit(top_k)
130
+ Args:
131
+ embedding_type: The type of embeddings to fetch
132
+
133
+ Returns:
134
+ List of (snippet_id, embedding) tuples
135
+
136
+ """
137
+ # Only select the fields we need and use a more efficient query
138
+ query = select(Embedding.snippet_id, Embedding.embedding).where(
139
+ Embedding.type == embedding_type
143
140
  )
144
141
  rows = await self.session.execute(query)
145
- return [(embedding.snippet_id, distance) for embedding, distance in rows.all()]
142
+ return [tuple(row) for row in rows.all()] # Convert Row objects to tuples
143
+
144
+ def prepare_vectors(
145
+ self, embeddings: list[tuple[int, list[float]]], query_embedding: list[float]
146
+ ) -> tuple[np.ndarray, np.ndarray]:
147
+ """Convert embeddings to numpy arrays.
146
148
 
149
+ Args:
150
+ embeddings: List of (snippet_id, embedding) tuples
151
+ query_embedding: Query embedding vector
147
152
 
148
- def cosine_similarity_json(
149
- col: Mapped[Any], query_vec: list[float]
150
- ) -> ColumnElement[Any]:
151
- """Calculate the cosine similarity using pure sqlalchemy.
153
+ Returns:
154
+ Tuple of (stored_vectors, query_vector) as numpy arrays
152
155
 
153
- Works for a *fixed-length* vector stored as a JSON array in SQLite.
154
- The calculation is done entirely in SQL using SQLite's JSON functions.
156
+ """
157
+ stored_vecs = np.array(
158
+ [emb[1] for emb in embeddings]
159
+ ) # Use index 1 to get embedding
160
+ query_vec = np.array(query_embedding)
161
+ return stored_vecs, query_vec
155
162
 
156
- Args:
157
- col: The column containing the JSON array of floats
158
- query_vec: The query vector to compare against
163
+ def compute_similarities(
164
+ self, stored_vecs: np.ndarray, query_vec: np.ndarray
165
+ ) -> np.ndarray:
166
+ """Compute cosine similarities between stored vectors and query vector.
159
167
 
160
- Returns:
161
- A SQLAlchemy expression that computes the cosine similarity
168
+ Args:
169
+ stored_vecs: Array of stored embedding vectors
170
+ query_vec: Query embedding vector
162
171
 
163
- """
164
- # Pre-compute query norm
165
- q_norm = math.sqrt(sum(x * x for x in query_vec))
166
-
167
- # Calculate dot product using JSON array functions
168
- dot = sum(
169
- cast(func.json_extract(col, f"$[{i}]"), Float) * literal(float(q))
170
- for i, q in enumerate(query_vec)
171
- )
172
-
173
- # Calculate row norm on the fly
174
- row_norm = func.sqrt(
175
- sum(
176
- cast(func.json_extract(col, f"$[{i}]"), Float)
177
- * cast(func.json_extract(col, f"$[{i}]"), Float)
178
- for i in range(len(query_vec))
179
- )
180
- )
172
+ Returns:
173
+ Array of similarity scores
174
+
175
+ """
176
+ stored_norms = np.linalg.norm(stored_vecs, axis=1)
177
+ query_norm = np.linalg.norm(query_vec)
178
+ return np.dot(stored_vecs, query_vec) / (stored_norms * query_norm)
179
+
180
+ def get_top_k_results(
181
+ self,
182
+ similarities: np.ndarray,
183
+ embeddings: list[tuple[int, list[float]]],
184
+ top_k: int,
185
+ ) -> list[tuple[int, float]]:
186
+ """Get top-k results by similarity score.
187
+
188
+ Args:
189
+ similarities: Array of similarity scores
190
+ embeddings: List of (snippet_id, embedding) tuples
191
+ top_k: Number of results to return
192
+
193
+ Returns:
194
+ List of (snippet_id, similarity_score) tuples
195
+
196
+ """
197
+ top_indices = np.argsort(similarities)[::-1][:top_k]
198
+ return [
199
+ (embeddings[i][0], float(similarities[i])) for i in top_indices
200
+ ] # Use index 0 to get snippet_id
201
+
202
+ async def list_semantic_results(
203
+ self, embedding_type: EmbeddingType, embedding: list[float], top_k: int = 10
204
+ ) -> list[tuple[int, float]]:
205
+ """List semantic results using cosine similarity.
206
+
207
+ This implementation fetches all embeddings of the given type and computes
208
+ cosine similarity in Python using NumPy for better performance.
209
+
210
+ Args:
211
+ embedding_type: The type of embeddings to search
212
+ embedding: The query embedding vector
213
+ top_k: Number of results to return
214
+
215
+ Returns:
216
+ List of (snippet_id, similarity_score) tuples, sorted by similarity
217
+
218
+ """
219
+ # Step 1: Fetch embeddings from database
220
+ embeddings = await self.fetch_embeddings(embedding_type)
221
+ if not embeddings:
222
+ return []
223
+
224
+ # Step 2: Convert to numpy arrays
225
+ stored_vecs, query_vec = self.prepare_vectors(embeddings, embedding)
226
+
227
+ # Step 3: Compute similarities
228
+ similarities = self.compute_similarities(stored_vecs, query_vec)
181
229
 
182
- # Calculate cosine similarity
183
- return (dot / (row_norm * literal(q_norm))).label("cosine_similarity")
230
+ # Step 4: Get top-k results
231
+ return self.get_top_k_results(similarities, embeddings, top_k)
@@ -0,0 +1,139 @@
1
+ """Benchmark script for semantic similarity search performance."""
2
+
3
+ import asyncio
4
+ from pathlib import Path
5
+ import random
6
+ import time
7
+ from typing import List
8
+
9
+ from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
10
+
11
+ from kodit.embedding.models import Embedding, EmbeddingType
12
+ from kodit.indexing.models import Index, Snippet
13
+ from kodit.retreival.repository import RetrievalRepository
14
+ from kodit.sources.models import File, Source
15
+
16
+
17
+ def generate_random_embedding(dim: int = 750) -> List[float]:
18
+ """Generate a random embedding vector of specified dimension."""
19
+ return [random.uniform(-1, 1) for _ in range(dim)]
20
+
21
+
22
+ async def setup_test_data(session: AsyncSession, num_embeddings: int = 5000) -> None:
23
+ """Set up test data with random embeddings."""
24
+ # Create a test index
25
+ source = Source(uri="test", cloned_path="test")
26
+ session.add(source)
27
+ await session.commit()
28
+ index = Index(source_id=source.id)
29
+ session.add(index)
30
+ await session.commit()
31
+ file = File(
32
+ uri="test",
33
+ cloned_path="test",
34
+ source_id=source.id,
35
+ )
36
+ session.add(file)
37
+ await session.commit()
38
+ snippet = Snippet(
39
+ file_id=file.id,
40
+ index_id=index.id,
41
+ content="This is a test snippet",
42
+ )
43
+ session.add(snippet)
44
+ await session.commit()
45
+
46
+ # Create test embeddings
47
+ embeddings = []
48
+ for i in range(num_embeddings):
49
+ embedding = Embedding(
50
+ snippet_id=snippet.id,
51
+ type=EmbeddingType.CODE,
52
+ embedding=generate_random_embedding(),
53
+ )
54
+ embeddings.append(embedding)
55
+
56
+ session.add_all(embeddings)
57
+ await session.commit()
58
+
59
+
60
+ async def run_benchmark(session: AsyncSession) -> None:
61
+ """Run the semantic search benchmark."""
62
+ # Setup test data
63
+ print("Setting up test data...")
64
+ await setup_test_data(session)
65
+
66
+ # Create repository instance
67
+ repo = RetrievalRepository(session)
68
+
69
+ # Generate a test query embedding
70
+ query_embedding = generate_random_embedding()
71
+
72
+ # Run the benchmark
73
+ num_runs = 10
74
+ total_time = 0
75
+ results = [] # Initialize results list
76
+
77
+ print("Running warm-up query...")
78
+ # Warm up
79
+ await repo.list_semantic_results(
80
+ embedding_type=EmbeddingType.CODE, embedding=query_embedding, top_k=10
81
+ )
82
+
83
+ print(f"\nRunning {num_runs} benchmark queries...")
84
+
85
+ # Actual benchmark
86
+ for i in range(num_runs):
87
+ start_time = time.perf_counter()
88
+ results = await repo.list_semantic_results(
89
+ embedding_type=EmbeddingType.CODE, embedding=query_embedding, top_k=10
90
+ )
91
+ end_time = time.perf_counter()
92
+ run_time = end_time - start_time
93
+ total_time += run_time
94
+ print(f"\nRun {i + 1}/{num_runs}: {run_time * 1000:.2f}ms")
95
+
96
+ # Calculate average time per run
97
+ avg_time = total_time / num_runs
98
+
99
+ print(f"\nSemantic Search Performance Results:")
100
+ print(f"Number of runs: {num_runs}")
101
+ print(f"Total execution time: {total_time:.2f} seconds")
102
+ print(f"Average time per query: {avg_time * 1000:.2f} ms")
103
+
104
+ # Print sample results
105
+ print(f"\nSample query returned {len(results)} results")
106
+ if results: # Add safety check
107
+ print(f"First result score: {results[0][1]:.4f}")
108
+
109
+
110
+ async def main():
111
+ """Main entry point for the benchmark."""
112
+ # Remove the database file if it exists
113
+ if Path("benchmark.db").exists():
114
+ Path("benchmark.db").unlink()
115
+
116
+ # Create async engine and session
117
+ engine = create_async_engine("sqlite+aiosqlite:///benchmark.db")
118
+
119
+ # Create tables
120
+ async with engine.begin() as conn:
121
+ await conn.run_sync(Source.metadata.create_all)
122
+ await conn.run_sync(File.metadata.create_all)
123
+ await conn.run_sync(Index.metadata.create_all)
124
+ await conn.run_sync(Snippet.metadata.create_all)
125
+ await conn.run_sync(Embedding.metadata.create_all)
126
+
127
+ # Create session factory
128
+ async_session = async_sessionmaker(engine, expire_on_commit=False)
129
+
130
+ # Run benchmark
131
+ async with async_session() as session:
132
+ await run_benchmark(session)
133
+
134
+ # Cleanup
135
+ await engine.dispose()
136
+
137
+
138
+ if __name__ == "__main__":
139
+ asyncio.run(main())
@@ -803,6 +803,7 @@ dev = [
803
803
  { name = "pytest-asyncio" },
804
804
  { name = "pytest-cov" },
805
805
  { name = "ruff" },
806
+ { name = "snakeviz" },
806
807
  ]
807
808
 
808
809
  [package.metadata]
@@ -842,6 +843,7 @@ dev = [
842
843
  { name = "pytest-asyncio", specifier = ">=0.26.0" },
843
844
  { name = "pytest-cov", specifier = ">=6.1.1" },
844
845
  { name = "ruff", specifier = ">=0.11.8" },
846
+ { name = "snakeviz", specifier = ">=2.2.2" },
845
847
  ]
846
848
 
847
849
  [[package]]
@@ -1939,6 +1941,18 @@ wheels = [
1939
1941
  { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
1940
1942
  ]
1941
1943
 
1944
+ [[package]]
1945
+ name = "snakeviz"
1946
+ version = "2.2.2"
1947
+ source = { registry = "https://pypi.org/simple/" }
1948
+ dependencies = [
1949
+ { name = "tornado" },
1950
+ ]
1951
+ sdist = { url = "https://files.pythonhosted.org/packages/04/06/82f56563b16d33c2586ac2615a3034a83a4ff1969b84c8d79339e5d07d73/snakeviz-2.2.2.tar.gz", hash = "sha256:08028c6f8e34a032ff14757a38424770abb8662fb2818985aeea0d9bc13a7d83", size = 182039, upload-time = "2024-11-09T22:03:58.99Z" }
1952
+ wheels = [
1953
+ { url = "https://files.pythonhosted.org/packages/cd/f7/83b00cdf4f114f10750a18b64c27dc34636d0ac990ccac98282f5c0fbb43/snakeviz-2.2.2-py3-none-any.whl", hash = "sha256:77e7b9c82f6152edc330040319b97612351cd9b48c706434c535c2df31d10ac5", size = 183477, upload-time = "2024-11-09T22:03:57.049Z" },
1954
+ ]
1955
+
1942
1956
  [[package]]
1943
1957
  name = "sniffio"
1944
1958
  version = "1.3.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes