ragit 0.7__py3-none-any.whl → 0.7.2__py3-none-any.whl

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.
@@ -0,0 +1,105 @@
1
+ #
2
+ # Copyright RODMENA LIMITED 2025
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ """
6
+ Ragit utilities module.
7
+ """
8
+
9
+ from collections import deque
10
+ from collections.abc import Hashable, Sequence
11
+ from datetime import datetime
12
+ from math import floor
13
+ from typing import Any
14
+
15
+ import pandas as pd
16
+
17
+
18
+ def get_hashable_repr(dct: dict[str, object]) -> tuple[tuple[str, object, float, int | None], ...]:
19
+ """
20
+ Returns a hashable representation of the provided dictionary.
21
+ """
22
+ queue: deque[tuple[str, object, float, int | None]] = deque((k, v, 0.0, None) for k, v in dct.items())
23
+ dict_unpacked: list[tuple[str, object, float, int | None]] = []
24
+ while queue:
25
+ key, val, lvl, p_ref = queue.pop()
26
+ if hasattr(val, "items"): # we have a nested dict
27
+ dict_unpacked.append((key, "+", lvl, p_ref))
28
+ if hash(key) != p_ref:
29
+ lvl += 1
30
+ queue.extendleft((k, v, lvl, hash(key)) for k, v in val.items())
31
+ elif isinstance(val, Hashable):
32
+ dict_unpacked.append((key, val, lvl, p_ref))
33
+ elif isinstance(val, Sequence):
34
+ dict_unpacked.append((key, "+", lvl, p_ref))
35
+ queue.extendleft((key, vv, floor(lvl) + ind * 0.01, hash(key)) for ind, vv in enumerate(val, 1))
36
+ else:
37
+ raise ValueError(f"Unsupported type in dict: {type(val)}")
38
+
39
+ return tuple(sorted(dict_unpacked, key=lambda it: (it[2], it[0])))
40
+
41
+
42
+ def remove_duplicates(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
43
+ """
44
+ Deduplicates list of provided dictionary items.
45
+
46
+ Parameters
47
+ ----------
48
+ items : list[dict]
49
+ List of items to deduplicate.
50
+
51
+ Returns
52
+ -------
53
+ list[dict]
54
+ A deduplicated list of input items.
55
+ """
56
+ duplicate_tracker = set()
57
+ deduplicated_items = []
58
+ for ind, elem in enumerate(map(get_hashable_repr, items)):
59
+ if elem not in duplicate_tracker:
60
+ duplicate_tracker.add(elem)
61
+ deduplicated_items.append(items[ind])
62
+ return deduplicated_items
63
+
64
+
65
+ def handle_missing_values_in_combinations(df: pd.DataFrame) -> pd.DataFrame:
66
+ """
67
+ Handle missing values in experiment data combinations.
68
+
69
+ Parameters
70
+ ----------
71
+ df : pd.DataFrame
72
+ Experiment data with combinations being explored.
73
+
74
+ Returns
75
+ -------
76
+ pd.DataFrame
77
+ Data with NaN values properly replaced.
78
+ """
79
+ if "chunk_overlap" in df.columns:
80
+ df["chunk_overlap"] = df["chunk_overlap"].map(lambda el: 0 if pd.isna(el) else el)
81
+
82
+ return df
83
+
84
+
85
+ def datetime_str_to_epoch_time(timestamp: str | int) -> str | int:
86
+ """
87
+ Convert datetime string to epoch time.
88
+
89
+ Parameters
90
+ ----------
91
+ timestamp : str | int
92
+ Either a datetime string or a unix timestamp.
93
+
94
+ Returns
95
+ -------
96
+ int
97
+ Unix timestamp or -1 if parsing fails.
98
+ """
99
+ if not isinstance(timestamp, str):
100
+ return timestamp
101
+ try:
102
+ iso_parseable = datetime.fromisoformat(timestamp)
103
+ except ValueError:
104
+ return -1
105
+ return int(iso_parseable.timestamp())
ragit/version.py ADDED
@@ -0,0 +1,5 @@
1
+ #
2
+ # Copyright RODMENA LIMITED 2025
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ __version__ = "0.7.2"
@@ -0,0 +1,480 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragit
3
+ Version: 0.7.2
4
+ Summary: Automatic RAG Pattern Optimization Engine
5
+ Author: RODMENA LIMITED
6
+ Maintainer-email: RODMENA LIMITED <info@rodmena.co.uk>
7
+ License-Expression: Apache-2.0
8
+ Project-URL: Homepage, https://github.com/rodmena-limited/ragit
9
+ Project-URL: Repository, https://github.com/rodmena-limited/ragit
10
+ Project-URL: Issues, https://github.com/rodmena-limited/ragit/issues
11
+ Keywords: AI,RAG,LLM,GenAI,Optimization,Ollama
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Operating System :: MacOS :: MacOS X
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Requires-Python: <3.14,>=3.12
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: requests>=2.31.0
23
+ Requires-Dist: numpy>=1.26.0
24
+ Requires-Dist: pandas>=2.2.0
25
+ Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: python-dotenv>=1.0.0
27
+ Requires-Dist: scikit-learn>=1.5.0
28
+ Requires-Dist: tqdm>=4.66.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: ragit[test]; extra == "dev"
31
+ Requires-Dist: pytest; extra == "dev"
32
+ Requires-Dist: pytest-cov; extra == "dev"
33
+ Requires-Dist: issuedb[web]; extra == "dev"
34
+ Requires-Dist: ruff; extra == "dev"
35
+ Requires-Dist: mypy; extra == "dev"
36
+ Provides-Extra: test
37
+ Requires-Dist: pytest; extra == "test"
38
+ Requires-Dist: pytest-cov; extra == "test"
39
+ Requires-Dist: pytest-mock; extra == "test"
40
+ Provides-Extra: docs
41
+ Requires-Dist: sphinx>=7.0; extra == "docs"
42
+ Requires-Dist: sphinx-rtd-theme>=2.0; extra == "docs"
43
+ Requires-Dist: sphinx-copybutton>=0.5; extra == "docs"
44
+ Dynamic: license-file
45
+
46
+ # ragit
47
+
48
+ A Python toolkit for building Retrieval-Augmented Generation (RAG) applications. Ragit provides document loading, chunking, vector search, and LLM integration out of the box, allowing you to build document Q&A systems and code generators with minimal boilerplate.
49
+
50
+ ## Table of Contents
51
+
52
+ 1. [Installation](#installation)
53
+ 2. [Configuration](#configuration)
54
+ 3. [Tutorial: Using Ragit](#tutorial-using-ragit)
55
+ - [Loading Documents](#loading-documents)
56
+ - [The RAGAssistant Class](#the-ragassistant-class)
57
+ - [Asking Questions](#asking-questions)
58
+ - [Generating Code](#generating-code)
59
+ - [Custom Retrieval](#custom-retrieval)
60
+ 4. [Tutorial: Platform Integration](#tutorial-platform-integration)
61
+ - [Flask Integration](#flask-integration)
62
+ - [FastAPI Integration](#fastapi-integration)
63
+ - [Command-Line Tools](#command-line-tools)
64
+ - [Batch Processing](#batch-processing)
65
+ 5. [Advanced: Hyperparameter Optimization](#advanced-hyperparameter-optimization)
66
+ 6. [API Reference](#api-reference)
67
+ 7. [License](#license)
68
+
69
+ ## Installation
70
+
71
+ ```bash
72
+ pip install ragit
73
+ ```
74
+
75
+ Ragit requires an Ollama-compatible API for embeddings and LLM inference. You can use:
76
+ - A local Ollama instance (https://ollama.ai)
77
+ - A cloud-hosted Ollama API
78
+ - Any OpenAI-compatible API endpoint
79
+
80
+ ## Configuration
81
+
82
+ Ragit reads configuration from environment variables. Create a `.env` file in your project root:
83
+
84
+ ```bash
85
+ # LLM API (cloud or local)
86
+ OLLAMA_BASE_URL=https://your-ollama-api.com
87
+ OLLAMA_API_KEY=your-api-key
88
+
89
+ # Embedding API (can be different from LLM)
90
+ OLLAMA_EMBEDDING_URL=http://localhost:11434
91
+
92
+ # Default models
93
+ RAGIT_DEFAULT_LLM_MODEL=llama3.1:8b
94
+ RAGIT_DEFAULT_EMBEDDING_MODEL=mxbai-embed-large
95
+ ```
96
+
97
+ A common setup is to use a cloud API for LLM inference (faster, more capable models) while running embeddings locally (lower latency, no API costs for indexing).
98
+
99
+ ## Tutorial: Using Ragit
100
+
101
+ This section covers the core functionality of ragit: loading documents, creating a RAG assistant, and querying your knowledge base.
102
+
103
+ ### Loading Documents
104
+
105
+ Ragit provides several functions for loading and chunking documents.
106
+
107
+ **Loading a single file:**
108
+
109
+ ```python
110
+ from ragit import load_text
111
+
112
+ doc = load_text("docs/api-reference.md")
113
+ print(doc.id) # "api-reference"
114
+ print(doc.content) # Full file contents
115
+ ```
116
+
117
+ **Loading a directory:**
118
+
119
+ ```python
120
+ from ragit import load_directory
121
+
122
+ # Load all markdown files
123
+ docs = load_directory("docs/", "*.md")
124
+
125
+ # Load recursively
126
+ docs = load_directory("docs/", "**/*.md", recursive=True)
127
+
128
+ # Load multiple file types
129
+ txt_docs = load_directory("docs/", "*.txt")
130
+ rst_docs = load_directory("docs/", "*.rst")
131
+ all_docs = txt_docs + rst_docs
132
+ ```
133
+
134
+ **Custom chunking:**
135
+
136
+ For fine-grained control over how documents are split:
137
+
138
+ ```python
139
+ from ragit import chunk_text, chunk_by_separator, chunk_rst_sections
140
+
141
+ # Fixed-size chunks with overlap
142
+ chunks = chunk_text(
143
+ text,
144
+ chunk_size=512, # Characters per chunk
145
+ chunk_overlap=50, # Overlap between chunks
146
+ doc_id="my-doc"
147
+ )
148
+
149
+ # Split by paragraph
150
+ chunks = chunk_by_separator(text, separator="\n\n")
151
+
152
+ # Split RST documents by section headers
153
+ chunks = chunk_rst_sections(rst_content, doc_id="tutorial")
154
+ ```
155
+
156
+ ### The RAGAssistant Class
157
+
158
+ The `RAGAssistant` class is the main interface for RAG operations. It handles document indexing, retrieval, and generation in a single object.
159
+
160
+ ```python
161
+ from ragit import RAGAssistant
162
+
163
+ # Create from a directory
164
+ assistant = RAGAssistant("docs/")
165
+
166
+ # Create from a single file
167
+ assistant = RAGAssistant("docs/tutorial.rst")
168
+
169
+ # Create from Document objects
170
+ from ragit import Document
171
+
172
+ docs = [
173
+ Document(id="intro", content="Introduction to the API..."),
174
+ Document(id="auth", content="Authentication uses JWT tokens..."),
175
+ Document(id="endpoints", content="Available endpoints: /users, /items..."),
176
+ ]
177
+ assistant = RAGAssistant(docs)
178
+ ```
179
+
180
+ **Configuration options:**
181
+
182
+ ```python
183
+ assistant = RAGAssistant(
184
+ "docs/",
185
+ embedding_model="mxbai-embed-large", # Model for embeddings
186
+ llm_model="llama3.1:70b", # Model for generation
187
+ chunk_size=512, # Characters per chunk
188
+ chunk_overlap=50, # Overlap between chunks
189
+ )
190
+ ```
191
+
192
+ ### Asking Questions
193
+
194
+ The `ask()` method retrieves relevant context and generates an answer:
195
+
196
+ ```python
197
+ assistant = RAGAssistant("docs/")
198
+
199
+ answer = assistant.ask("How do I authenticate API requests?")
200
+ print(answer)
201
+ ```
202
+
203
+ **Customizing the query:**
204
+
205
+ ```python
206
+ answer = assistant.ask(
207
+ "How do I authenticate API requests?",
208
+ top_k=5, # Number of chunks to retrieve
209
+ temperature=0.3, # Lower = more focused answers
210
+ system_prompt="You are a technical documentation assistant. "
211
+ "Answer concisely and include code examples."
212
+ )
213
+ ```
214
+
215
+ ### Generating Code
216
+
217
+ The `generate_code()` method is optimized for producing clean, runnable code:
218
+
219
+ ```python
220
+ assistant = RAGAssistant("framework-docs/")
221
+
222
+ code = assistant.generate_code(
223
+ "Create a REST API endpoint for user registration",
224
+ language="python"
225
+ )
226
+ print(code)
227
+ ```
228
+
229
+ The output is clean code without markdown formatting. The assistant uses your documentation as context to generate framework-specific, idiomatic code.
230
+
231
+ ### Custom Retrieval
232
+
233
+ For advanced use cases, you can access the retrieval and generation steps separately:
234
+
235
+ ```python
236
+ assistant = RAGAssistant("docs/")
237
+
238
+ # Step 1: Retrieve relevant chunks
239
+ results = assistant.retrieve("authentication", top_k=5)
240
+ for chunk, score in results:
241
+ print(f"Score: {score:.3f}")
242
+ print(f"Content: {chunk.content[:200]}...")
243
+ print()
244
+
245
+ # Step 2: Get formatted context string
246
+ context = assistant.get_context("authentication", top_k=3)
247
+
248
+ # Step 3: Generate with custom prompt
249
+ prompt = f"""Based on this documentation:
250
+
251
+ {context}
252
+
253
+ Write a Python function that validates a JWT token."""
254
+
255
+ response = assistant.generate(
256
+ prompt,
257
+ system_prompt="You are an expert Python developer.",
258
+ temperature=0.2
259
+ )
260
+ ```
261
+
262
+ ## Tutorial: Platform Integration
263
+
264
+ This section shows how to integrate ragit into web applications and other platforms.
265
+
266
+ ### Flask Integration
267
+
268
+ ```python
269
+ from flask import Flask, request, jsonify
270
+ from ragit import RAGAssistant
271
+
272
+ app = Flask(__name__)
273
+
274
+ # Initialize once at startup
275
+ assistant = RAGAssistant("docs/")
276
+
277
+ @app.route("/ask", methods=["POST"])
278
+ def ask():
279
+ data = request.get_json()
280
+ question = data.get("question", "")
281
+
282
+ if not question:
283
+ return jsonify({"error": "question is required"}), 400
284
+
285
+ answer = assistant.ask(question, top_k=3)
286
+ return jsonify({"answer": answer})
287
+
288
+ @app.route("/search", methods=["GET"])
289
+ def search():
290
+ query = request.args.get("q", "")
291
+ top_k = int(request.args.get("top_k", 5))
292
+
293
+ results = assistant.retrieve(query, top_k=top_k)
294
+ return jsonify({
295
+ "results": [
296
+ {"content": chunk.content, "score": score}
297
+ for chunk, score in results
298
+ ]
299
+ })
300
+
301
+ if __name__ == "__main__":
302
+ app.run(debug=True)
303
+ ```
304
+
305
+ ### FastAPI Integration
306
+
307
+ ```python
308
+ from fastapi import FastAPI, HTTPException
309
+ from pydantic import BaseModel
310
+ from ragit import RAGAssistant
311
+
312
+ app = FastAPI()
313
+
314
+ # Initialize once at startup
315
+ assistant = RAGAssistant("docs/")
316
+
317
+ class Question(BaseModel):
318
+ question: str
319
+ top_k: int = 3
320
+ temperature: float = 0.7
321
+
322
+ class Answer(BaseModel):
323
+ answer: str
324
+
325
+ @app.post("/ask", response_model=Answer)
326
+ async def ask(q: Question):
327
+ if not q.question.strip():
328
+ raise HTTPException(status_code=400, detail="question is required")
329
+
330
+ answer = assistant.ask(
331
+ q.question,
332
+ top_k=q.top_k,
333
+ temperature=q.temperature
334
+ )
335
+ return Answer(answer=answer)
336
+
337
+ @app.get("/search")
338
+ async def search(q: str, top_k: int = 5):
339
+ results = assistant.retrieve(q, top_k=top_k)
340
+ return {
341
+ "results": [
342
+ {"content": chunk.content, "score": score}
343
+ for chunk, score in results
344
+ ]
345
+ }
346
+ ```
347
+
348
+ ### Command-Line Tools
349
+
350
+ Build CLI tools using argparse or click:
351
+
352
+ ```python
353
+ #!/usr/bin/env python3
354
+ import argparse
355
+ from ragit import RAGAssistant
356
+
357
+ def main():
358
+ parser = argparse.ArgumentParser(description="Query documentation")
359
+ parser.add_argument("question", help="Question to ask")
360
+ parser.add_argument("--docs", default="docs/", help="Documentation path")
361
+ parser.add_argument("--top-k", type=int, default=3, help="Context chunks")
362
+ args = parser.parse_args()
363
+
364
+ assistant = RAGAssistant(args.docs)
365
+ answer = assistant.ask(args.question, top_k=args.top_k)
366
+ print(answer)
367
+
368
+ if __name__ == "__main__":
369
+ main()
370
+ ```
371
+
372
+ Usage:
373
+
374
+ ```bash
375
+ python ask.py "How do I configure logging?"
376
+ python ask.py "What are the API rate limits?" --docs api-docs/ --top-k 5
377
+ ```
378
+
379
+ ### Batch Processing
380
+
381
+ Process multiple questions or generate reports:
382
+
383
+ ```python
384
+ from ragit import RAGAssistant
385
+
386
+ assistant = RAGAssistant("docs/")
387
+
388
+ questions = [
389
+ "What authentication methods are supported?",
390
+ "How do I handle errors?",
391
+ "What are the rate limits?",
392
+ ]
393
+
394
+ # Process questions
395
+ results = {}
396
+ for question in questions:
397
+ results[question] = assistant.ask(question)
398
+
399
+ # Generate a report
400
+ with open("qa-report.md", "w") as f:
401
+ f.write("# Documentation Q&A Report\n\n")
402
+ for question, answer in results.items():
403
+ f.write(f"## {question}\n\n")
404
+ f.write(f"{answer}\n\n")
405
+ ```
406
+
407
+ ## Advanced: Hyperparameter Optimization
408
+
409
+ Ragit includes tools to find the optimal RAG configuration for your specific documents and use case.
410
+
411
+ ```python
412
+ from ragit import RagitExperiment, Document, BenchmarkQuestion
413
+
414
+ # Your documents
415
+ documents = [
416
+ Document(id="auth", content="Authentication uses Bearer tokens..."),
417
+ Document(id="api", content="The API supports GET, POST, PUT, DELETE..."),
418
+ ]
419
+
420
+ # Benchmark questions with expected answers
421
+ benchmark = [
422
+ BenchmarkQuestion(
423
+ question="What authentication method does the API use?",
424
+ ground_truth="The API uses Bearer token authentication."
425
+ ),
426
+ BenchmarkQuestion(
427
+ question="What HTTP methods are supported?",
428
+ ground_truth="GET, POST, PUT, and DELETE methods are supported."
429
+ ),
430
+ ]
431
+
432
+ # Run optimization
433
+ experiment = RagitExperiment(documents, benchmark)
434
+ results = experiment.run(max_configs=20)
435
+
436
+ # Get the best configuration
437
+ best = results[0]
438
+ print(f"Best config: chunk_size={best.config.chunk_size}, "
439
+ f"chunk_overlap={best.config.chunk_overlap}, "
440
+ f"top_k={best.config.top_k}")
441
+ print(f"Score: {best.score:.3f}")
442
+ ```
443
+
444
+ The experiment tests different combinations of chunk sizes, overlaps, and retrieval parameters to find what works best for your content.
445
+
446
+ ## API Reference
447
+
448
+ ### Document Loading
449
+
450
+ | Function | Description |
451
+ |----------|-------------|
452
+ | `load_text(path)` | Load a single text file as a Document |
453
+ | `load_directory(path, pattern, recursive=False)` | Load files matching a glob pattern |
454
+ | `chunk_text(text, chunk_size, chunk_overlap, doc_id)` | Split text into overlapping chunks |
455
+ | `chunk_document(doc, chunk_size, chunk_overlap)` | Split a Document into chunks |
456
+ | `chunk_by_separator(text, separator, doc_id)` | Split text by a delimiter |
457
+ | `chunk_rst_sections(text, doc_id)` | Split RST by section headers |
458
+
459
+ ### RAGAssistant
460
+
461
+ | Method | Description |
462
+ |--------|-------------|
463
+ | `retrieve(query, top_k=3)` | Return list of (Chunk, score) tuples |
464
+ | `get_context(query, top_k=3)` | Return formatted context string |
465
+ | `generate(prompt, system_prompt, temperature)` | Generate text without retrieval |
466
+ | `ask(question, system_prompt, top_k, temperature)` | Retrieve context and generate answer |
467
+ | `generate_code(request, language, top_k, temperature)` | Generate clean code |
468
+
469
+ ### Properties
470
+
471
+ | Property | Description |
472
+ |----------|-------------|
473
+ | `assistant.num_documents` | Number of loaded documents |
474
+ | `assistant.num_chunks` | Number of indexed chunks |
475
+ | `assistant.embedding_model` | Current embedding model |
476
+ | `assistant.llm_model` | Current LLM model |
477
+
478
+ ## License
479
+
480
+ Apache-2.0 - RODMENA LIMITED
@@ -0,0 +1,18 @@
1
+ ragit/__init__.py,sha256=PjQogIWMlydZFWVECqhmxw-X9i7lEXdUTe2XlT6qYUQ,2213
2
+ ragit/assistant.py,sha256=lXjZRUr_WsYLP3XLOktabgfPVyKOZPdREzyL7cSRufk,11251
3
+ ragit/config.py,sha256=uKLchJQHjH8MImZ2OJahDjSzyasFqgrFb9Z4aHxJ7og,1495
4
+ ragit/loaders.py,sha256=keusuPzXPBiLDVj4hKfPCcge-rm-cnzNRk50fGXvTJs,5571
5
+ ragit/version.py,sha256=5FNjmLKNB4z5E3tbMqwASQafUUFSWnaxBRb0EsQPVK8,97
6
+ ragit/core/__init__.py,sha256=j53PFfoSMXwSbK1rRHpMbo8mX2i4R1LJ5kvTxBd7-0w,100
7
+ ragit/core/experiment/__init__.py,sha256=4vAPOOYlY5Dcr2gOolyhBSPGIUxZKwEkgQffxS9BodA,452
8
+ ragit/core/experiment/experiment.py,sha256=Qh1NJkY9LbKaidRfiI8GOwBZqopjK-MSVBuD_JEgO-k,16582
9
+ ragit/core/experiment/results.py,sha256=KHpN3YSLJ83_JUfIMccRPS-q7LEt0S9p8ehDRawk_4k,3487
10
+ ragit/providers/__init__.py,sha256=iliJt74Lt3mFUlKGfSFW-D0cMonUygY6sRZ6lLjeU7M,435
11
+ ragit/providers/base.py,sha256=MJ8mVeXuGWhkX2XGTbkWIY3cVoTOPr4h5XBXw8rAX2Q,3434
12
+ ragit/providers/ollama.py,sha256=epmXiJ06jCWFERWFMdCNr8FP6WubxdHXj8dUn71WJuQ,9502
13
+ ragit/utils/__init__.py,sha256=-UsE5oJSnmEnBDswl-ph0A09Iu8yKNbPhd1-_7Lcb8Y,3051
14
+ ragit-0.7.2.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
15
+ ragit-0.7.2.dist-info/METADATA,sha256=TZ_VuZbe4GKkb8Jo8tC_NgT3hcu7e-uy7S9w6kpURlE,13662
16
+ ragit-0.7.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ ragit-0.7.2.dist-info/top_level.txt,sha256=pkPbG7yrw61wt9_y_xcLE2vq2a55fzockASD0yq0g4s,6
18
+ ragit-0.7.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5