Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,13 @@
visibleCheckdropdown = !visibleCheckdropdown;
"
>
Check data <i v-if="tableJSON?.schema?.is_latest === false">!</i>
Check data
</BaseButton>
</span></template
>
<template #dropdown-content
><span>
<BaseButton @click.prevent="$emit('updateValidValues', true)"> Ignore errors </BaseButton>
<BaseButton
v-if="tableJSON?.schema?.is_latest === false"
@click.prevent="fetchValidation({ latest: true })"
>
Fetch latest schema
</BaseButton>
</span></template
>
</BaseDropdown>
Expand Down Expand Up @@ -175,7 +169,7 @@ export default {
handler(newValidation, oldValidation) {
if (this.isLoaded) {
if (this.editable)
console.warn("Changes validation", this.tableJSON.schema.schemaName, this.tableJSON.schema.version_tag);
console.warn("Changes validation", this.tableJSON.schema.schemaName);
this.tabulator?.setColumns(this.columnsConfig);
this.validateTable();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,14 @@ export const useSchemaTableViewModel = (props: {
);
const groupbyColumns = ref(refColumns.value || null);

const fetchValidation = async ({ latest = false }: { latest?: boolean } = {}) => {
const fetchValidation = async () => {
var schemaName: string = props.tableJSON?.schema?.schemaName || props.tableJSON?.validation?.name;
if (!schemaName) {
return;
}
var version_id: string = latest ? null : props.tableJSON.schema?.version_id;
await waitForAsyncValue(() => dataset.workspaceName);

const [schema, fileMetadata] = await getSchema.fetch(dataset.workspaceName, schemaName, version_id);
const [schema, fileMetadata] = await getSchema.fetch(dataset.workspaceName, schemaName);

// const schemaMetadataUpdate = {
// ...props.tableJSON.schema,
Expand All @@ -61,8 +60,7 @@ export const useSchemaTableViewModel = (props: {
props.tableJSON?.schema?.fields || [],
props.tableJSON?.schema?.primaryKey || [],
fileMetadata,
schemaName,
version_id
schemaName
);

if (!isEqual(props.tableJSON?.schema, schemaMetadataUpdate)) {
Expand Down
5 changes: 0 additions & 5 deletions extralit-frontend/v1/domain/entities/IAnswer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,9 @@ export type TableAnswer = {
metadata?: {
schemaName?: string;
etag?: string;
version_id?: string;
last_modified?: Date;
version_tag?: string;
is_latest?: boolean;
};
schemaName?: string;
version_id?: string;
is_latest?: boolean;
};
reference?: string;
validation?: {
Expand Down
17 changes: 1 addition & 16 deletions extralit-frontend/v1/domain/entities/table/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,20 @@ export interface DataFrameField {
export interface FileMetadata {
schemaName?: string;
etag?: string;
version_id?: string;
last_modified?: Date;
version_tag?: string;
is_latest?: boolean;
}

export class DataFrameSchema {
primaryKey: string[];
fields: DataFrameField[];
metadata?: FileMetadata;
schemaName?: string;
version_id?: string;
version_tag?: string;
is_latest?: boolean;

constructor(
fields: DataFrameField[] = [],
primaryKey: string[] = [],
metadata?: FileMetadata,
schemaName?: string,
version_id?: string,
is_latest?: boolean
) {
constructor(fields: DataFrameField[] = [], primaryKey: string[] = [], metadata?: FileMetadata, schemaName?: string) {
this.fields = fields;
this.primaryKey = primaryKey;
this.metadata = metadata;
this.schemaName = schemaName;
this.version_id = version_id;
this.is_latest = is_latest;
}

get fieldNames(): string[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,16 @@ const FILES_API_ERRORS = {
export class GetExtractionSchemaUseCase {
constructor(private readonly axios: AxiosInstance) {}

async fetch(
workspaceName: string,
schemaName: string,
versionId?: string
): Promise<[ValidationSchema, FileMetadata]> {
async fetch(workspaceName: string, schemaName: string): Promise<[ValidationSchema, FileMetadata]> {
try {
const url = `/v1/file/${workspaceName}/schemas/${schemaName}`;
const response = await this.axios.get<ValidationSchema>(url, {
params: {
version_id: versionId,
},
});
const response = await this.axios.get<ValidationSchema>(url);
const headers = response.headers as AxiosHeaders;
const schema = response.data;
let isLatest = null;
const headerValue = headers.get("is-latest");
if (headerValue === "true") {
isLatest = true;
} else if (headerValue === "false") {
isLatest = false;
}

const SchemaMetadata: FileMetadata = {
schemaName,
etag: headers.get("etag") as string,
version_id: headers.get("version-id") as string,
version_tag: headers.get("version-tag") as string,
is_latest: isLatest,
last_modified: new Date((headers.get("last-modified") as string) || ""),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ describe("SchemaRepository", () => {
dataset_id: "s-1",
version: 1,
object_key: "k",
object_version_id: null,
etag: "e",
checksum: "c",
parent_version_id: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ interface BackendSchemaVersion {
dataset_id: string;
version: number;
object_key: string;
object_version_id: string | null;
etag: string;
checksum: string;
parent_version_id: string | null;
Expand Down
2 changes: 0 additions & 2 deletions extralit-server/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ dependencies = [
# Async SQLite
"aiosqlite == 0.20.0",
# Statics server
"aiofiles >= 24.1.0",
"PyYAML >= 5.4.1,< 6.1.0",
# security dependencies
"python-jose[cryptography] >= 3.3.0",
Expand Down Expand Up @@ -71,7 +70,6 @@ dependencies = [
"Jinja2>=3.1.4", # Used by huggingface-hub to render dataset card templates
# For file storage
"aioboto3>=13.1.1",
"types-aiobotocore-s3==2.24.2",
# For document processing
"ocrmypdf>=16.11.0",
"pdf2image>=1.17.0",
Expand Down
16 changes: 7 additions & 9 deletions extralit-server/src/extralit_server/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
from extralit_server._version import __version__ as extralit_version
from extralit_server.api.routes import api_v1
from extralit_server.constants import DEFAULT_API_KEY, DEFAULT_PASSWORD, DEFAULT_USERNAME
from extralit_server.contexts import accounts, files
from extralit_server.contexts import accounts, buckets, files
from extralit_server.database import get_async_db
from extralit_server.helpers import create_s3_client, shared_resources
from extralit_server.helpers import shared_resources
from extralit_server.jobs.queues import REDIS_CONNECTION
from extralit_server.logging import configure_logging
from extralit_server.models import User, Workspace
Expand All @@ -47,14 +47,12 @@ async def app_lifespan(app: FastAPI):
configure_redis()

try:
await create_s3_client()
track_server_startup()
yield
finally:
# Clean up S3 client if it exists
s3_client = shared_resources.get("s3_client")
if s3_client:
await s3_client.__aexit__(None, None, None)
storage = shared_resources.get("storage")
if storage:
await storage.aclose()
shared_resources.clear()


Expand Down Expand Up @@ -304,8 +302,8 @@ async def _create_oauth_allowed_workspaces(db: AsyncSession):
if await Workspace.get_by(db, name=allowed_workspace.name) is None:
_LOGGER.info(f"Creating workspace with name {allowed_workspace.name!r}")
try:
client = await files.get_s3_client()
await files.create_bucket(client, allowed_workspace.name)
storage = await files.get_storage()
await buckets.create(storage, allowed_workspace.name)
except Exception as e:
_LOGGER.error(f"Failed to create bucket for workspace {allowed_workspace.name!r}: {e}")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""drop schema_versions.object_version_id

Revision ID: a1c9f4e2b703
Revises: 13da2d87e660
Create Date: 2026-08-18 09:00:00.000000

The column recorded the S3 native object version of a published schema body. It was
written best-effort and never read: a version's identity is `(dataset_id, version)`,
allocated under a row lock so every version lands on its own key, and its integrity is
`checksum`. Object-store versioning is being removed wholesale, so the column goes with it.
"""

import sqlalchemy as sa
from alembic import op

revision = "a1c9f4e2b703"
down_revision = "13da2d87e660"
branch_labels = None
depends_on = None


def upgrade() -> None:
with op.batch_alter_table("schema_versions") as batch_op:
batch_op.drop_column("object_version_id")


def downgrade() -> None:
with op.batch_alter_table("schema_versions") as batch_op:
batch_op.add_column(sa.Column("object_version_id", sa.Text(), nullable=True))
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ async def publish_schema_version(
dataset_id: UUID,
version_create: SchemaVersionCreate,
db: Annotated[AsyncSession, Depends(get_async_db)],
s3_client=Depends(files_ctx.get_s3_client),
storage=Depends(files_ctx.get_storage),
current_user: Annotated[User, Security(auth.get_current_user)],
):
dataset = await Dataset.get_or_raise(db, dataset_id, options=[selectinload(Dataset.workspace)])
await authorize(current_user, DatasetPolicy.publish(dataset))

return await schema_versions.publish_version(
db,
s3_client,
storage,
dataset,
body=version_create.body,
# One bucket per workspace, named exactly Workspace.name — contexts/files.py:381.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from extralit_server.api.schemas.v1.documents import DocumentCreate, DocumentDelete, DocumentListItem, DocumentUpdate
from extralit_server.api.schemas.v1.imports import DocumentsBulkCreate, DocumentsBulkResponse
from extralit_server.contexts import files, imports
from extralit_server.contexts.ocr import storage
from extralit_server.contexts.ocr import storage as layout_storage
from extralit_server.contexts.ocr.projection import project_layout
from extralit_server.database import get_async_db
from extralit_server.models import User, Workspace
Expand All @@ -36,7 +36,7 @@ async def add_document(
document_create: Annotated[str, Form()],
file_data: Annotated[UploadFile | None, File()] = None,
db: AsyncSession = Depends(get_async_db),
s3_client=Depends(files.get_s3_client),
storage=Depends(files.get_storage),
current_user: User = Security(auth.get_current_user),
):
await authorize(current_user, DocumentPolicy.create())
Expand Down Expand Up @@ -80,7 +80,7 @@ async def add_document(
document_new.file_name = file_data.filename

file_url = await files.put_document_file(
s3_client=s3_client,
storage=storage,
workspace_name=workspace.name,
document_id=document_new.id, # type: ignore[arg-type]
file_data=await file_data.read(),
Expand Down Expand Up @@ -129,7 +129,7 @@ async def get_document(
doi: Annotated[str | None, Query(description="DOI")] = None,
limit: Annotated[int | None, Query(description="Maximum number of documents to return")] = None,
db: AsyncSession = Depends(get_async_db),
s3_client=Depends(files.get_s3_client),
storage=Depends(files.get_storage),
current_user: User = Security(auth.get_current_user),
) -> list[DocumentListItem]:
await authorize(current_user, DocumentPolicy.get())
Expand Down Expand Up @@ -208,7 +208,7 @@ async def delete_documents_by_workspace_id(
workspace_id: UUID,
document_delete: Annotated[DocumentDelete | None, Body()] = None,
db: AsyncSession = Depends(get_async_db),
s3_client=Depends(files.get_s3_client),
storage=Depends(files.get_storage),
current_user: User = Security(auth.get_current_user),
):
await authorize(current_user, DocumentPolicy.delete(workspace_id))
Expand All @@ -231,7 +231,7 @@ async def delete_documents_by_workspace_id(

_LOGGER.info(f"Deleting {len(documents)} documents")
for document in documents:
await files.delete_document_artifacts(s3_client, workspace.name, document.id)
await files.delete_document_artifacts(storage, workspace.name, document.id)

return len(documents)

Expand Down Expand Up @@ -261,7 +261,7 @@ async def get_document_layout(
document_id: Annotated[UUID, Path(title="The UUID of the document whose layout will be retrieved")],
pages: Annotated[list[int] | None, Query(description="1-indexed pages to include")] = None,
labels: Annotated[list[str] | None, Query(description="DocItemLabels to include, e.g. `table`")] = None,
s3_client=Depends(files.get_s3_client),
storage=Depends(files.get_storage),
current_user: User = Security(auth.get_current_user),
) -> DocumentLayoutOut:
await authorize(current_user, DocumentPolicy.get())
Expand Down Expand Up @@ -291,8 +291,8 @@ async def get_document_layout(
)

try:
doc = await storage.load_layout(
s3_client,
doc = await layout_storage.load_layout(
storage,
workspace.name,
document_id,
object_path=layout_metadata.get("layout_url"),
Expand Down
Loading
Loading