Faster PostgreSQL bulk inserts by copying CSV into a table via SQLAlchemy.
Project description
fullmetalcopy
Fast PostgreSQL bulk loads from CSV using the server-side copy path, wired through SQLAlchemy 2 so you keep your normal engines and connection patterns.
PyPI: fullmetalcopy · Source: GitHub
Why use this?
Bulk-inserting rows with plain INSERT is slow for large datasets. This library streams CSV bytes into a table via PostgreSQL’s efficient copy-style ingestion, with thin wrappers over psycopg3, psycopg2, or asyncpg depending on your SQLAlchemy URL.
Requirements
| Python | 3.10+ |
| Database | PostgreSQL only (checked at runtime) |
| Core | SQLAlchemy ≥ 2.0 |
Install at least one driver extra (or bring your own compatible stack):
| Extra | Use case |
|---|---|
psycopg |
Sync or async with psycopg v3 (postgresql+psycopg://…, postgresql+psycopg_async://…) |
psycopg2 |
Sync with psycopg2 (postgresql+psycopg2://…) |
asyncpg |
Async with asyncpg (postgresql+asyncpg://…) |
pandas / polars |
Optional; same extras as declared in pyproject.toml if you combine with dataframe tooling |
Install
pip install fullmetalcopy
# Drivers (pick what matches your engine URL)
pip install "fullmetalcopy[psycopg]"
pip install "fullmetalcopy[psycopg2]"
pip install "fullmetalcopy[asyncpg]"
# Examples with dataframe extras
pip install "fullmetalcopy[psycopg,pandas]"
pip install "fullmetalcopy[asyncpg,polars]"
Public API
Import from the package root:
import fullmetalcopy as fc
fc.__version__ # package version string
fc.copy_from_csv # synchronous
fc.async_copy_from_csv # asynchronous (alias of the async module’s copy_from_csv)
Both callables accept a binary CSV stream (io.BytesIO, open(..., "rb"), or any typing.BinaryIO).
| Argument | Default | Meaning |
|---|---|---|
connection / async_connection |
— | SQLAlchemy Connection or AsyncConnection |
csv_file |
— | Binary stream positioned at the start of the CSV payload |
table_name |
— | Target table name (unqualified unless you use schema) |
sep |
"," |
Field delimiter |
null |
"" |
See CSV and null semantics below |
columns |
None |
Optional explicit column list (order must match CSV columns after the header is applied) |
headers |
True |
If True, the first line is treated as a header row and skipped from data (column names may still be taken from the file or from columns) |
schema |
None |
PostgreSQL schema name; search_path is not permanently changed |
Driver is inferred from connection.dialect.driver. Non-PostgreSQL dialects raise a clear error.
Usage examples
Synchronous (psycopg or psycopg2)
import io
import sqlalchemy as sa
import fullmetalcopy as fc
engine = sa.create_engine("postgresql+psycopg2://scott:tiger@hostname/dbname")
with io.BytesIO() as buf:
buf.write(b"id,name\n1,Ada\n")
buf.seek(0)
with engine.connect() as conn:
fc.copy_from_csv(conn, buf, "people")
conn.commit()
Asynchronous (asyncpg or psycopg)
import io
from sqlalchemy.ext.asyncio import create_async_engine
import fullmetalcopy as fc
async def main() -> None:
engine = create_async_engine("postgresql+asyncpg://scott:tiger@hostname/dbname")
try:
async with engine.connect() as conn:
with io.BytesIO() as buf:
buf.write(b"id,name\n1,Ada\n")
buf.seek(0)
await fc.async_copy_from_csv(conn, buf, "people")
await conn.commit()
finally:
await engine.dispose()
Non-default schema
fc.copy_from_csv(conn, buf, "invoices", schema="billing")
await fc.async_copy_from_csv(conn, buf, "invoices", schema="billing")
Header row plus explicit column order
When the file has a header but you want to pin the column list (and skip the header line the same way on every backend):
fc.copy_from_csv(
conn,
buf,
"people",
columns=["id", "name"],
headers=True,
)
Driver behavior (summary)
| Backend | Sync | Async | Notes |
|---|---|---|---|
| psycopg v3 | Yes | Yes | Rows are read with Python’s csv module and sent with copy.write_row; column count must match. |
| psycopg2 | Yes | No | Uses cursor.copy_from. For schema=…, uses SET LOCAL search_path then an unqualified table name (reliable with COPY). |
| asyncpg | No | Yes | Uses copy_to_table. Header skipping is aligned with other backends (adapt_names); asyncpg is called with header=False after any header line is consumed. |
For full detail, see the docstrings in fullmetalcopy.synchronous.copycsv and fullmetalcopy.asynchronous.copycsv, and the per-driver modules under fullmetalcopy.synchronous / fullmetalcopy.asynchronous.
CSV and null semantics
- The header line (when
headers=True) is decoded as UTF-8 and parsed withcsv.reader, so quoted fields in the header are handled like normal CSV. - On the psycopg3 path, each data row is also read with
csv.readerand must have exactly as many fields as the resolved column list, or aValueErroris raised. - On the psycopg3 path, any cell that equals the
nullstring is stored as SQLNULL. The defaultnull=""maps empty fields toNULL. If you need to keep empty strings as empty strings, setnullto a sentinel that does not appear in real data (for example"\N"where appropriate).
PostgreSQL’s CSV rules and Python’s csv module are very close but not identical for exotic cases (embedded newlines, unusual escapes). For maximum fidelity on huge files, consider a raw server-side COPY … FROM STDIN pipeline; this library targets typical spreadsheet-style CSV.
Development
From a clone of the repository:
pip install -e ".[dev]"
ruff format .
ruff check .
mypy fullmetalcopy
pytest
[dev] pulls all supported drivers, pytest, Ruff, Mypy, and testing.postgresql for the test suite.
Releasing
- Align the version in
pyproject.tomlandfullmetalcopy/__init__.py. - Update
CHANGELOG.md. - Run
ruff format .,ruff check .,mypy fullmetalcopy, andpytest. - Build artifacts:
python -m build(optionallytwine check dist/*). - Tag:
git tag -s v0.2.0 -m "Release 0.2.0"thengit push origin v0.2.0. - Upload to PyPI (trusted publishing or
twine upload dist/*).
License
MIT — see LICENSE. Release history: CHANGELOG.md.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fullmetalcopy-0.2.0.tar.gz.
File metadata
- Download URL: fullmetalcopy-0.2.0.tar.gz
- Upload date:
- Size: 14.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d07562489c5de615c2771d1dcf1b5bc205a291ff0527d51da026cb9b76381bcd
|
|
| MD5 |
5dfa83889b2701a2b6b2b508337915da
|
|
| BLAKE2b-256 |
a710497d11e5a271f5114103fbfee2f0957dcde93fde405fdf74e4ccf6e43f2f
|
File details
Details for the file fullmetalcopy-0.2.0-py3-none-any.whl.
File metadata
- Download URL: fullmetalcopy-0.2.0-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a6bad390867233f146bcae14a5e876e43a9b091f095fdb50584afb0e16d95755
|
|
| MD5 |
dffda2a0f4d1e749d01a5147586dbaa5
|
|
| BLAKE2b-256 |
001b66fe109ff8cd0c648e20514f57c951f986b12f77da65f5c3c12cfed38dd2
|