Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/examples/gallery_streaming.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
# Streaming messages
"""# Streaming messages

Ragna supports streaming responses from the assistant. This example showcases how this
is performed using the Python and REST API.
Expand Down
7 changes: 3 additions & 4 deletions docs/tutorials/gallery_custom_components.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
# Custom Components
"""# Custom Components

While Ragna has builtin support for a few [source storages][ragna.source_storages]
and [assistants][ragna.assistants], its real strength lies in allowing users
Expand Down Expand Up @@ -84,7 +83,7 @@ def retrieve(
# [streaming example](../../generated/examples/gallery_streaming.md) for more
# information.

from typing import Iterator
from collections.abc import Iterator

from ragna.core import Assistant, Source

Expand Down Expand Up @@ -402,7 +401,7 @@ def answer(

import asyncio
import time
from typing import AsyncIterator
from collections.abc import AsyncIterator


class AsyncAssistant(Assistant):
Expand Down
3 changes: 1 addition & 2 deletions docs/tutorials/gallery_python_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
# Python API
"""# Python API

The [Python API](../../references/python-api.md) is the best place to get started with
Ragna and understand its key components. It's also the best way to continue
Expand Down
14 changes: 8 additions & 6 deletions docs/tutorials/gallery_rest_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
# REST API
"""# REST API

Ragna was designed to help you quickly build custom RAG powered web
applications. For this you can leverage the built-in
Expand Down Expand Up @@ -117,10 +116,13 @@
# - The field name is the ID of the document returned by step 1.
# - The field value is the binary content of the document.

client.put(
"/api/documents",
files=[("documents", (documents[0]["id"], open(document_path, "rb")))],
)
with open(document_path, "rb") as f:
print(
client.put(
"/api/documents",
files=[("documents", (documents[0]["id"], f))],
)
)

# %%
# ## Step 4: Select a source storage and assistant
Expand Down
3 changes: 1 addition & 2 deletions docs/tutorials/gallery_web_ui.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
# Web UI
"""# Web UI

Ragna comes with a web UI where you can try out all of the features!

Expand Down
4 changes: 2 additions & 2 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 36 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -177,22 +177,51 @@ depends-on = [
"build",
]

[tool.ruff]
target-version = "py310"

[tool.ruff.lint]

pydocstyle = { convention = "google" }

select = [
"E",
"F",
# import sorting
"I001"
"I001",
"B",
"C4",
"ISC",
"RET",
"SIM",
"PTH",
"D2",
"UP",
"ASYNC",
]

ignore = [
# Conflicts with ruff formatter
"ISC001",
# Ignore line too long, because due to black, the error can only occur for strings
"E501",
# cache has its purpose
"B019",
# built-in open() is well understood
"PTH123",
# mutually-exclusive with D211
"D203",
# mutually-exclusive with D212
"D213",
]
# Ignore line too long, because due to black, the error can only occur for strings
ignore = ["E501"]

[tool.ruff.lint.per-file-ignores]
# ignore unused imports and imports not at the top of the file in __init__.py files
"__init__.py" = ["F401", "E402"]
# The examples often have imports below the top of the file to follow the narrative
"docs/examples/**/*.py" = ["E402", "F704", "I001"]
"docs/tutorials/**/*.py" = ["E402", "F704", "I001"]
# The examples and tutorials need to have a good the narrative rather than follow our code-style rules
"docs/examples/**/*.py" = ["E402", "F704", "I001", "D"]
"docs/tutorials/**/*.py" = ["E402", "F704", "I001", "D"]
# blocking code in async tests is not an issue
"tests/**/*.py" = ["ASYNC101"]

[tool.pytest.ini_options]
minversion = "6.0"
Expand Down
2 changes: 1 addition & 1 deletion ragna/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
except ModuleNotFoundError:
import warnings

warnings.warn("ragna was not properly installed!")
warnings.warn("ragna was not properly installed!", stacklevel=2)
del warnings

__version__ = "UNKNOWN"
Expand Down
25 changes: 13 additions & 12 deletions ragna/_cli/config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import itertools
from collections import defaultdict
from collections.abc import Iterable
from pathlib import Path
from types import ModuleType
from typing import Annotated, Iterable, Type, TypeVar, cast
from typing import Annotated, TypeVar, cast

import pydantic
import questionary
Expand All @@ -27,19 +28,19 @@
def parse_config(value: str) -> Config:
try:
config = Config.from_file(value)
except RagnaException:
except RagnaException as exc:
rich.print(f"The configuration file {value} does not exist.")
if value == "./ragna.toml":
rich.print(
"If you don't have a configuration file yet, "
"run [bold]ragna init[/bold] to generate one."
)
raise typer.Exit(1)
raise typer.Exit(1) from exc
except pydantic.ValidationError as validation:
# FIXME: pretty formatting!
for error in validation.errors():
rich.print(error)
raise typer.Exit(1)
raise typer.Exit(1) from validation
# This stores the original value so we can pass it on to subprocesses that we might
# start.
config.__ragna_cli_config_path__ = value # type: ignore[attr-defined]
Expand Down Expand Up @@ -157,8 +158,8 @@ def _wizard_builtin() -> Config:
def _select_components(
title: str,
module: ModuleType,
base_cls: Type[T],
) -> list[Type[T]]:
base_cls: type[T],
) -> list[type[T]]:
components = sorted(
(
obj
Expand All @@ -170,7 +171,7 @@ def _select_components(
key=lambda component: component.display_name(),
)
return cast(
list[Type[T]],
list[type[T]],
questionary.checkbox(
f"Which {title} do you want to use?",
choices=[
Expand All @@ -186,13 +187,13 @@ def _select_components(
)


def _handle_unmet_requirements(components: Iterable[Type[Component]]) -> None:
unmet_requirements = set(
def _handle_unmet_requirements(components: Iterable[type[Component]]) -> None:
unmet_requirements = {
requirement
for component in components
for requirement in component.requirements()
if not requirement.is_available()
)
}
if not unmet_requirements:
return

Expand Down Expand Up @@ -331,7 +332,7 @@ def check_config(config: Config) -> bool:
("source storages", config.source_storages),
("assistants", config.assistants),
]:
components = cast(list[Type[Component]], components)
components = cast(list[type[Component]], components)

table = Table(
"",
Expand Down Expand Up @@ -361,7 +362,7 @@ def check_config(config: Config) -> bool:

def _split_requirements(
requirements: Iterable[Requirement],
) -> dict[Type[Requirement], list[Requirement]]:
) -> dict[type[Requirement], list[Requirement]]:
split_reqs = defaultdict(list)
for req in requirements:
split_reqs[type(req)].append(req)
Expand Down
6 changes: 3 additions & 3 deletions ragna/_cli/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Annotated, Optional
from typing import Annotated

import rich
import typer
Expand Down Expand Up @@ -30,7 +30,7 @@ def version_callback(value: bool) -> None:
@app.callback()
def _main(
version: Annotated[
Optional[bool],
bool | None,
typer.Option(
"--version", callback=version_callback, help="Show version and exit."
),
Expand Down Expand Up @@ -97,7 +97,7 @@ def deploy(
),
] = False,
open_browser: Annotated[
Optional[bool],
bool | None,
typer.Option(
help="Open a browser when Ragna is deployed.",
show_default="value of ui / no-ui",
Expand Down
35 changes: 13 additions & 22 deletions ragna/_cli/corpus.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
import sys
from pathlib import Path
from typing import Annotated, Optional
from typing import Annotated

import rich
import typer
Expand Down Expand Up @@ -45,7 +45,7 @@ def experimental_warning() -> None:
def ingest(
documents: list[Path],
metadata_fields: Annotated[
Optional[Path],
Path | None,
typer.Option(
help="JSON file that contains mappings from document name "
"to metadata fields associated with a document.",
Expand All @@ -57,7 +57,7 @@ def ingest(
] = "default",
config: ConfigOption = "./ragna.toml", # type: ignore[assignment]
user: Annotated[
Optional[str],
str | None,
typer.Option(help="User to link the documents to in the ragna database."),
] = None,
report_failures: Annotated[
Expand All @@ -69,12 +69,12 @@ def ingest(
] = False,
) -> None:
try:
document_factory = getattr(config.document, "from_path")
except AttributeError:
document_factory = config.document.from_path # type: ignore[attr-defined]
except AttributeError as exc:
raise typer.BadParameter(
f"{config.document.__name__} does not support creating documents from a"
f"path. Please implement a `from_path` method."
)
) from exc

database = Database(config.database_url)
core_to_schema_document = CoreToSchemaConverter().document
Expand All @@ -83,10 +83,10 @@ def ingest(
try:
with open(metadata_fields) as file:
metadata = json.load(file)
except Exception:
except Exception as exc:
raise typer.BadParameter(
f"Could not read the metadata fields file: {metadata_fields}"
)
) from exc
else:
metadata = {}

Expand Down Expand Up @@ -132,14 +132,10 @@ def ingest(
document_instances = []

if source_storage.display_name() in ingestion_log:
batch_doc_set = set(
[
str(doc)
for doc in documents[
batch_number : batch_number + BATCH_SIZE
]
]
)
batch_doc_set = {
str(doc)
for doc in documents[batch_number : batch_number + BATCH_SIZE]
}
if batch_doc_set.issubset(
ingestion_log[source_storage.display_name()]
):
Expand All @@ -150,12 +146,7 @@ def ingest(
try:
document_instances.append(
document_factory(
document,
metadata=(
metadata[str(document)]
if str(document) in metadata
else None
),
document, metadata=metadata.get(str(document))
)
)
except Exception:
Expand Down
4 changes: 2 additions & 2 deletions ragna/_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import tempfile
import textwrap
from pathlib import Path
from typing import Any, Optional, cast
from typing import Any, cast

import httpx

Expand Down Expand Up @@ -114,7 +114,7 @@ def get_http_client(
*,
authenticate: bool = False,
upload_sample_document: bool = False,
) -> tuple[httpx.Client, Optional[dict[str, Any]]]:
) -> tuple[httpx.Client, dict[str, Any] | None]:
if upload_sample_document and not authenticate:
raise RagnaException(
"Cannot upload a document without authenticating first. "
Expand Down
Loading