ci: integrate pyo3

ci: add manylinux

fix: maturin build error

ci: add wheels upload

ci: use venv
This commit is contained in:
JeanArhancet 2024-08-19 16:35:14 +02:00
parent 93964c6655
commit fc2962e04e
9 changed files with 134 additions and 55 deletions

47
.github/workflows/python.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: Python
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.12']
runs-on: ${{ matrix.os }}
env:
working-directory: ./bindings/python
defaults:
run:
working-directory: ${{ env.working-directory }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: install rust stable
uses: dtolnay/rust-toolchain@stable
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: pip install -r requirements.txt && pip install -r requirements-dev.txt
- name: Run Ruff check
run: ruff check limbo tests
- name: Install limbo
run: pip install -e .
- name: Run Pytest
run: pytest tests

1
.gitignore vendored
View file

@ -15,6 +15,7 @@ venv
env
.env
.venv
dist/
# OS
.DS_Store

View file

@ -17,7 +17,7 @@ extension-module = ["pyo3/extension-module"]
[dependencies]
anyhow = "1.0"
limbo_core = { path = "../../core" }
pyo3 = { version = "0.22.2", features = ["anyhow", "auto-initialize"] }
pyo3 = { version = "0.22.2", features = ["anyhow"] }
[build-dependencies]
version_check = "0.9.5"

View file

@ -1,4 +1,4 @@
from _limbo import (
from ._limbo import (
Connection,
Cursor,
DatabaseError,
@ -14,16 +14,16 @@ from _limbo import (
)
__all__ = [
"__version__",
"Connection",
"Cursor",
"InterfaceError",
"DatabaseError",
"DataError",
"OperationalError",
"IntegrityError",
"InternalError",
"ProgrammingError",
"NotSupportedError",
"connect",
'__version__',
'Connection',
'Cursor',
'InterfaceError',
'DatabaseError',
'DataError',
'OperationalError',
'IntegrityError',
'InternalError',
'ProgrammingError',
'NotSupportedError',
'connect',
]

View file

@ -35,7 +35,6 @@ dynamic = [
[project.optional-dependencies]
dev = [
"maturin==1.7.0",
"black==24.4.2",
"isort==5.13.2",
"mypy==1.11.0",
@ -50,6 +49,8 @@ Source = "https://github.com/penberg/limbo"
[tool.maturin]
bindings = 'pyo3'
module-name = "limbo._limbo"
features = ["pyo3/extension-module"]
[tool.ruff]
line-length = 120
@ -70,7 +71,6 @@ quote-style = 'single'
testpaths = 'tests'
log_format = '%(name)s %(levelname)s: %(message)s'
[tool.coverage.run]
source = ['limbo']
branch = true

View file

@ -14,8 +14,6 @@ iniconfig==2.0.0
# via pytest
isort==5.13.2
# via limbo (pyproject.toml)
maturin==1.7.0
# via limbo (pyproject.toml)
mypy==1.11.0
# via limbo (pyproject.toml)
mypy-extensions==1.0.0

View file

@ -0,0 +1,8 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --strip-extras pyproject.toml
#
typing-extensions==4.12.2
# via limbo (pyproject.toml)

View file

@ -119,22 +119,23 @@ impl Cursor {
let mut smt_lock = smt.lock().map_err(|_| {
PyErr::new::<OperationalError, _>("Failed to acquire statement lock")
})?;
match smt_lock
.step()
.map_err(|e| PyErr::new::<OperationalError, _>(format!("Step error: {:?}", e)))?
{
limbo_core::RowResult::Row(row) => {
let py_row = row_to_py(py, &row);
Ok(Some(py_row))
loop {
match smt_lock.step().map_err(|e| {
PyErr::new::<OperationalError, _>(format!("Step error: {:?}", e))
})? {
limbo_core::RowResult::Row(row) => {
let py_row = row_to_py(py, &row);
return Ok(Some(py_row));
}
limbo_core::RowResult::IO => {
self.conn.io.run_once().map_err(|e| {
PyErr::new::<OperationalError, _>(format!("IO error: {:?}", e))
})?;
}
limbo_core::RowResult::Done => {
return Ok(None);
}
}
limbo_core::RowResult::IO => {
self.conn.io.run_once().map_err(|e| {
PyErr::new::<OperationalError, _>(format!("IO error: {:?}", e))
})?;
Ok(None)
}
limbo_core::RowResult::Done => Ok(None),
}
} else {
Err(PyErr::new::<ProgrammingError, _>("No statement prepared for execution").into())
@ -143,10 +144,33 @@ impl Cursor {
pub fn fetchall(&mut self, py: Python) -> Result<Vec<PyObject>> {
let mut results = Vec::new();
while let Some(row) = self.fetchone(py)? {
results.push(row);
if let Some(smt) = &self.smt {
let mut smt_lock = smt.lock().map_err(|_| {
PyErr::new::<OperationalError, _>("Failed to acquire statement lock")
})?;
loop {
match smt_lock.step().map_err(|e| {
PyErr::new::<OperationalError, _>(format!("Step error: {:?}", e))
})? {
limbo_core::RowResult::Row(row) => {
let py_row = row_to_py(py, &row);
results.push(py_row);
}
limbo_core::RowResult::IO => {
self.conn.io.run_once().map_err(|e| {
PyErr::new::<OperationalError, _>(format!("IO error: {:?}", e))
})?;
}
limbo_core::RowResult::Done => {
return Ok(results);
}
}
}
} else {
Err(PyErr::new::<ProgrammingError, _>("No statement prepared for execution").into())
}
Ok(results)
}
pub fn close(&self) -> Result<()> {

View file

@ -5,53 +5,54 @@ import pytest
import limbo
@pytest.mark.parametrize("provider", ["sqlite3", "limbo"])
@pytest.mark.parametrize('provider', ['sqlite3', 'limbo'])
def test_fetchall_select_all_users(provider):
conn = connect(provider, "tests/database.db")
conn = connect(provider, 'tests/database.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
cursor.execute('SELECT * FROM users')
users = cursor.fetchall()
assert users
assert users == [(1, "alice"), (2, "bob")]
assert users == [(1, 'alice'), (2, 'bob')]
@pytest.mark.parametrize(
"provider",
'provider',
[
"sqlite3",
'sqlite3',
'limbo'
],
)
def test_fetchall_select_user_ids(provider):
conn = connect(provider, "tests/database.db")
conn = connect(provider, 'tests/database.db')
cursor = conn.cursor()
cursor.execute("SELECT id FROM users")
cursor.execute('SELECT id FROM users')
user_ids = cursor.fetchall()
assert user_ids
assert user_ids == [(1,), (2,)]
@pytest.mark.parametrize("provider", ["sqlite3", "limbo"])
@pytest.mark.parametrize('provider', ['sqlite3', 'limbo'])
def test_fetchone_select_all_users(provider):
conn = connect(provider, "tests/database.db")
conn = connect(provider, 'tests/database.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
cursor.execute('SELECT * FROM users')
alice = cursor.fetchone()
assert alice
assert alice == (1, "alice")
assert alice == (1, 'alice')
bob = cursor.fetchone()
assert bob
assert bob == (2, "bob")
assert bob == (2, 'bob')
@pytest.mark.parametrize("provider", ["sqlite3", "limbo"])
@pytest.mark.parametrize('provider', ['sqlite3', 'limbo'])
def test_fetchone_select_max_user_id(provider):
conn = connect(provider, "tests/database.db")
conn = connect(provider, 'tests/database.db')
cursor = conn.cursor()
cursor.execute("SELECT MAX(id) FROM users")
cursor.execute('SELECT MAX(id) FROM users')
max_id = cursor.fetchone()
assert max_id
@ -59,8 +60,8 @@ def test_fetchone_select_max_user_id(provider):
def connect(provider, database):
if provider == "limbo":
if provider == 'limbo':
return limbo.connect(database)
if provider == "sqlite3":
if provider == 'sqlite3':
return sqlite3.connect(database)
raise Exception(f"Provider `{provider}` is not supported")
raise Exception(f'Provider `{provider}` is not supported')