Skip to content

Import

Full attribute and method reference for the import system, generated from docstrings. For a task-oriented walkthrough, see Export & Import.

starlette_admin.importers.ImportConfig dataclass

Security and capacity limits applied by the import endpoint.

Attributes:

Name Type Description
max_upload_size int

Maximum accepted file size in bytes (default: 10 MB). Checked before any parsing begins.

max_rows int | None

Maximum number of rows that may be imported in a single request (default: 100,000). The file is counted in a pre-pass before any record is created; when it holds more rows, the endpoint returns HTTP 400 so no partial import happens. Set to None to disable the cap entirely.

Source code in starlette_admin/importers/base.py
@dataclass
class ImportConfig:
    """Security and capacity limits applied by the import endpoint.

    Attributes:
        max_upload_size: Maximum accepted file size in bytes (default: 10 MB).
            Checked before any parsing begins.
        max_rows: Maximum number of rows that may be imported in a single
            request (default: 100,000). The file is counted in a pre-pass
            before any record is created; when it holds more rows, the
            endpoint returns HTTP 400 so no partial import happens. Set to
            ``None`` to disable the cap entirely.
    """

    max_upload_size: int = 10 * 1024 * 1024
    max_rows: int | None = 100_000

starlette_admin.importers.ImportContext dataclass

Everything an importer needs to process one upload.

Attributes:

Name Type Description
fields list[BaseField]

Importable fields on the view (the same set used for export, resolved to the IMPORT request action).

content bytes

The raw bytes of the main data file (CSV, XLSX, JSON, etc.).

view BaseModelView

The model view performing the import.

request Request

The originating HTTP request.

dry_run bool

When True, the operation will parse and validate without committing any records. In this mode, :attr:ImportResult.rows_created and :attr:ImportResult.rows_updated will be 0.

selected_fields list[str] | None

Field names to read from the uploaded file, or None for all of fields. Columns for any other field are dropped before validation.

update_existing bool

When True, a row whose primary key matches an existing record updates it instead of creating a new one.

Source code in starlette_admin/importers/base.py
@dataclass
class ImportContext:
    """Everything an importer needs to process one upload.

    Attributes:
        fields: Importable fields on the view (the same set used for export, resolved to the ``IMPORT`` request action).
        content: The raw bytes of the main data file (CSV, XLSX, JSON, etc.).
        view: The model view performing the import.
        request: The originating HTTP request.
        dry_run: When ``True``, the operation will parse and validate without committing any records. In this mode, :attr:`ImportResult.rows_created` and :attr:`ImportResult.rows_updated` will be 0.
        selected_fields: Field names to read from the uploaded file, or ``None`` for all of ``fields``. Columns for any other field are dropped before validation.
        update_existing: When ``True``, a row whose primary key matches an existing record updates it instead of creating a new one.
    """

    fields: list[BaseField]
    content: bytes
    view: BaseModelView
    request: Request
    dry_run: bool = False
    selected_fields: list[str] | None = None
    update_existing: bool = False

starlette_admin.importers.ImportResult dataclass

Summary returned after a (live or dry-run) import operation.

Attributes:

Name Type Description
rows_total int

The total number of data rows parsed from the source file.

rows_created int

The number of records successfully created (always 0 during a dry-run).

rows_updated int

The number of records updated in upsert mode (always 0 during a dry-run).

rows_skipped int

The number of valid rows that were intentionally skipped (e.g., unchanged in upsert mode).

errors list[ImportRowError]

Per-row validation and processing errors collected during the operation.

dry_run bool

Indicates whether no data was actually committed.

Source code in starlette_admin/importers/base.py
@dataclass
class ImportResult:
    """Summary returned after a (live or dry-run) import operation.

    Attributes:
        rows_total: The total number of data rows parsed from the source file.
        rows_created: The number of records successfully created (always 0 during a dry-run).
        rows_updated: The number of records updated in upsert mode (always 0 during a dry-run).
        rows_skipped: The number of valid rows that were intentionally skipped (e.g., unchanged in upsert mode).
        errors: Per-row validation and processing errors collected during the operation.
        dry_run: Indicates whether no data was actually committed.
    """

    rows_total: int = 0
    rows_created: int = 0
    rows_updated: int = 0
    rows_skipped: int = 0
    errors: list[ImportRowError] = dc_field(default_factory=list)
    dry_run: bool = False

    @property
    def has_errors(self) -> bool:
        """Returns True if any row produced an error."""
        return bool(self.errors)

    @property
    def rows_ok(self) -> int:
        """The number of rows processed without error (created + updated + skipped)."""
        return self.rows_created + self.rows_updated + self.rows_skipped

has_errors property

Returns True if any row produced an error.

rows_ok property

The number of rows processed without error (created + updated + skipped).

starlette_admin.importers.ImportRowError dataclass

A validation or processing error for a single row.

Attributes:

Name Type Description
row int

The 1-based row number in the source file. Use 0 for file-level errors (e.g., unrecognized format, missing required column).

field str | None

The name of the offending field, or None for row-level errors.

message str

A human-readable description of the problem.

Source code in starlette_admin/importers/base.py
@dataclass
class ImportRowError:
    """A validation or processing error for a single row.

    Attributes:
        row: The 1-based row number in the source file. Use ``0`` for file-level errors (e.g., unrecognized format, missing required column).
        field: The name of the offending field, or ``None`` for row-level errors.
        message: A human-readable description of the problem.
    """

    row: int
    field: str | None
    message: str

starlette_admin.importers.BaseImporter

Bases: ABC

Base class for all import-format implementations.

Subclasses implement :meth:parse to yield parsed row dictionaries from the raw format bytes.

Minimal subclass example::

class CsvImporter(BaseImporter):
    extension = "csv"

    async def parse(
        self, ctx: ImportContext
    ) -> AsyncGenerator[dict[str, Any], None]:
        text = ctx.content.decode()
        reader = csv.DictReader(io.StringIO(text))
        for row in reader:
            yield dict(row)

File fields (:class:~starlette_admin.fields.FileField, :class:~starlette_admin.fields.ImageField) are always excluded from import; see exclude_from_import on :class:~starlette_admin.fields.BaseField.

Source code in starlette_admin/importers/base.py
class BaseImporter(ABC):
    """Base class for all import-format implementations.

    Subclasses implement :meth:`parse` to yield parsed row dictionaries from the raw format bytes.

    **Minimal subclass example**::

        class CsvImporter(BaseImporter):
            extension = "csv"

            async def parse(
                self, ctx: ImportContext
            ) -> AsyncGenerator[dict[str, Any], None]:
                text = ctx.content.decode()
                reader = csv.DictReader(io.StringIO(text))
                for row in reader:
                    yield dict(row)

    File fields (:class:`~starlette_admin.fields.FileField`,
    :class:`~starlette_admin.fields.ImageField`) are always excluded from
    import; see ``exclude_from_import`` on :class:`~starlette_admin.fields.BaseField`.
    """

    extension: str = "bin"
    format_key: str = ""  # URL/form param value; defaults to extension when empty
    requires: str | None = None  # pip extra needed, or None if always available

    @abstractmethod
    def parse(
        self,
        ctx: ImportContext,
    ) -> AsyncGenerator[dict[str, Any]]:
        """Yield parsed row dictionaries from ``ctx.content``.

        Each yielded dictionary maps column headers (or field names) to raw string values.
        """
        raise NotImplementedError(
            "Sub-classes must implement parse()"
        )  # pragma: no cover

parse(ctx) abstractmethod

Yield parsed row dictionaries from ctx.content.

Each yielded dictionary maps column headers (or field names) to raw string values.

Source code in starlette_admin/importers/base.py
@abstractmethod
def parse(
    self,
    ctx: ImportContext,
) -> AsyncGenerator[dict[str, Any]]:
    """Yield parsed row dictionaries from ``ctx.content``.

    Each yielded dictionary maps column headers (or field names) to raw string values.
    """
    raise NotImplementedError(
        "Sub-classes must implement parse()"
    )  # pragma: no cover

starlette_admin.importers.CsvImporter

Bases: BaseImporter

Source code in starlette_admin/importers/csv.py
class CsvImporter(BaseImporter):
    extension = "csv"

    def __init__(self, **fmtparams: Any) -> None:
        """
        Args:
            fmtparams: Forwarded to ``csv.DictReader`` (``delimiter``,
                ``quotechar``, ``quoting``, ...).
        """
        self.fmtparams = fmtparams

    async def parse(self, ctx: ImportContext) -> AsyncGenerator[dict[str, Any]]:
        _log.debug("CSV parse starting: %d bytes", len(ctx.content))
        # utf-8-sig strips the BOM written by Excel
        text = ctx.content.decode("utf-8-sig")
        reader = csv.DictReader(io.StringIO(text), **self.fmtparams)
        row_count = 0
        for row in reader:
            row_count += 1
            yield dict(row)
        _log.debug("CSV parse complete: %d row(s) yielded", row_count)

__init__(**fmtparams)

Parameters:

Name Type Description Default
fmtparams Any

Forwarded to csv.DictReader (delimiter, quotechar, quoting, ...).

{}
Source code in starlette_admin/importers/csv.py
def __init__(self, **fmtparams: Any) -> None:
    """
    Args:
        fmtparams: Forwarded to ``csv.DictReader`` (``delimiter``,
            ``quotechar``, ``quoting``, ...).
    """
    self.fmtparams = fmtparams

starlette_admin.importers.TsvImporter

Bases: CsvImporter

Source code in starlette_admin/importers/csv.py
class TsvImporter(CsvImporter):
    extension = "tsv"

    def __init__(self, **fmtparams: Any) -> None:
        """
        Args:
            fmtparams: Forwarded to ``csv.DictReader``. ``delimiter`` defaults
                to a tab and may be overridden.
        """
        fmtparams.setdefault("delimiter", "\t")
        super().__init__(**fmtparams)

__init__(**fmtparams)

Parameters:

Name Type Description Default
fmtparams Any

Forwarded to csv.DictReader. delimiter defaults to a tab and may be overridden.

{}
Source code in starlette_admin/importers/csv.py
def __init__(self, **fmtparams: Any) -> None:
    """
    Args:
        fmtparams: Forwarded to ``csv.DictReader``. ``delimiter`` defaults
            to a tab and may be overridden.
    """
    fmtparams.setdefault("delimiter", "\t")
    super().__init__(**fmtparams)

starlette_admin.importers.TablibImporter

Bases: BaseImporter

Parses through tablib.Dataset for any format tablib supports.

Source code in starlette_admin/importers/tablib.py
class TablibImporter(BaseImporter):
    """Parses through ``tablib.Dataset`` for any format tablib supports."""

    def __init__(self, format: str, **import_kwargs: Any) -> None:
        """
        Args:
            format: A tablib import format name (``"xlsx"``, ``"yaml"``, ...).
            import_kwargs: Forwarded to ``tablib.Dataset.load()``.
        """
        self.format = format
        self.extension = format
        self.requires = _REQUIRES.get(format, "tablib")
        self.import_kwargs = import_kwargs

    async def parse(self, ctx: ImportContext) -> AsyncGenerator[dict[str, Any]]:
        try:
            import tablib
        except ImportError as exc:
            raise ImportError(
                f"The 'tablib' package is required for {self.format!r} import. "
                f"Install it with: pip install {self.requires}"
            ) from exc

        _log.debug("%s parse starting: %d bytes", self.format, len(ctx.content))
        content: bytes | str = ctx.content
        if self.format in _TEXT_FORMATS:
            content = ctx.content.decode("utf-8")
        dataset = tablib.Dataset()
        dataset.load(content, format=self.format, **self.import_kwargs)
        row_count = 0
        for row in dataset:
            row_count += 1
            yield dict(zip(dataset.headers, row))
        _log.debug("%s parse complete: %d row(s) yielded", self.format, row_count)

__init__(format, **import_kwargs)

Parameters:

Name Type Description Default
format str

A tablib import format name ("xlsx", "yaml", ...).

required
import_kwargs Any

Forwarded to tablib.Dataset.load().

{}
Source code in starlette_admin/importers/tablib.py
def __init__(self, format: str, **import_kwargs: Any) -> None:
    """
    Args:
        format: A tablib import format name (``"xlsx"``, ``"yaml"``, ...).
        import_kwargs: Forwarded to ``tablib.Dataset.load()``.
    """
    self.format = format
    self.extension = format
    self.requires = _REQUIRES.get(format, "tablib")
    self.import_kwargs = import_kwargs

starlette_admin.importers.JsonImporter

Bases: BaseImporter

Source code in starlette_admin/importers/json.py
class JsonImporter(BaseImporter):
    extension = "json"

    async def parse(self, ctx: ImportContext) -> AsyncGenerator[dict[str, Any]]:
        _log.debug("JSON parse starting: %d bytes", len(ctx.content))
        data = json.loads(ctx.content.decode("utf-8"))
        if not isinstance(data, list):
            _log.warning(
                "JSON import rejected: root value is %s, expected array",
                type(data).__name__,
            )
            raise ValueError("JSON import expects a top-level array of objects.")
        _log.debug("JSON parse: %d item(s) in array", len(data))
        for item in data:
            yield dict(item)