mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-30 13:24:22 +01:00
Merge 'Use UV more in python related scripts and actions' from Pedro Muniz
This PR initializes an UV project in `antithesis_tests` so that we can have an easier time to track dependencies and build pylimbo automatically for our environment. Consequently, making it easier to create new antithesis tests in the future with better IDE support. Also modified our Github actions to check python linting with Ruff, and removed unnecessary Python jobs. With that, I applied the Ruff fixes which is the cause of the many file changes. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #1782
This commit is contained in:
51
.github/workflows/python.yml
vendored
51
.github/workflows/python.yml
vendored
@@ -55,58 +55,37 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt -r requirements-dev.txt
|
||||
- name: Install uv
|
||||
uses: useblacksmith/setup-uv@v4
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Install Limbo
|
||||
run: pip install -e .
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev
|
||||
|
||||
- name: Run Pytest
|
||||
run: pytest tests
|
||||
run: uvx pytest tests
|
||||
|
||||
lint:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ env.working-directory }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: useblacksmith/setup-python@v6
|
||||
|
||||
- name: Install uv
|
||||
uses: useblacksmith/setup-uv@v4
|
||||
with:
|
||||
python-version: "3.12"
|
||||
enable-cache: true
|
||||
|
||||
- name: Install dev dependencies
|
||||
run: pip install -r requirements-dev.txt
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev --all-packages
|
||||
|
||||
- name: Run lint
|
||||
run: make lint
|
||||
|
||||
check-requirements:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ env.working-directory }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: useblacksmith/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install pip-tools
|
||||
run: pip install pip-tools
|
||||
|
||||
# - name: Check requirements files
|
||||
# run: make check-requirements
|
||||
- name: Ruff lint
|
||||
run: uvx ruff check
|
||||
|
||||
linux:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import limbo
|
||||
from antithesis.random import get_random
|
||||
from antithesis.assertions import always
|
||||
|
||||
try:
|
||||
@@ -12,20 +11,16 @@ except Exception as e:
|
||||
|
||||
cur = con.cursor()
|
||||
|
||||
initial_state = cur.execute(f'''
|
||||
initial_state = cur.execute("""
|
||||
SELECT * FROM initial_state
|
||||
''').fetchone()
|
||||
""").fetchone()
|
||||
|
||||
curr_total = cur.execute(f'''
|
||||
curr_total = cur.execute("""
|
||||
SELECT SUM(balance) AS total FROM accounts;
|
||||
''').fetchone()
|
||||
""").fetchone()
|
||||
|
||||
always(
|
||||
initial_state[1] == curr_total[0],
|
||||
'[Anytime] Initial balance always equals current balance',
|
||||
{
|
||||
'init_bal': initial_state[1],
|
||||
'curr_bal': curr_total[0]
|
||||
}
|
||||
initial_state[1] == curr_total[0],
|
||||
"[Anytime] Initial balance always equals current balance",
|
||||
{"init_bal": initial_state[1], "curr_bal": curr_total[0]},
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import limbo
|
||||
from antithesis.random import get_random
|
||||
from antithesis.assertions import always
|
||||
|
||||
try:
|
||||
@@ -12,20 +11,16 @@ except Exception as e:
|
||||
|
||||
cur = con.cursor()
|
||||
|
||||
initial_state = cur.execute(f'''
|
||||
initial_state = cur.execute("""
|
||||
SELECT * FROM initial_state
|
||||
''').fetchone()
|
||||
""").fetchone()
|
||||
|
||||
curr_total = cur.execute(f'''
|
||||
curr_total = cur.execute("""
|
||||
SELECT SUM(balance) AS total FROM accounts;
|
||||
''').fetchone()
|
||||
""").fetchone()
|
||||
|
||||
always(
|
||||
initial_state[1] == curr_total[0],
|
||||
'[Eventually] Initial balance always equals current balance',
|
||||
{
|
||||
'init_bal': initial_state[1],
|
||||
'curr_bal': curr_total[0]
|
||||
}
|
||||
initial_state[1] == curr_total[0],
|
||||
"[Eventually] Initial balance always equals current balance",
|
||||
{"init_bal": initial_state[1], "curr_bal": curr_total[0]},
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import limbo
|
||||
from antithesis.random import get_random
|
||||
from antithesis.assertions import always
|
||||
|
||||
try:
|
||||
@@ -12,20 +11,16 @@ except Exception as e:
|
||||
|
||||
cur = con.cursor()
|
||||
|
||||
initial_state = cur.execute(f'''
|
||||
initial_state = cur.execute("""
|
||||
SELECT * FROM initial_state
|
||||
''').fetchone()
|
||||
""").fetchone()
|
||||
|
||||
curr_total = cur.execute(f'''
|
||||
curr_total = cur.execute("""
|
||||
SELECT SUM(balance) AS total FROM accounts;
|
||||
''').fetchone()
|
||||
""").fetchone()
|
||||
|
||||
always(
|
||||
initial_state[1] == curr_total[0],
|
||||
'[Finally] Initial balance always equals current balance',
|
||||
{
|
||||
'init_bal': initial_state[1],
|
||||
'curr_bal': curr_total[0]
|
||||
}
|
||||
initial_state[1] == curr_total[0],
|
||||
"[Finally] Initial balance always equals current balance",
|
||||
{"init_bal": initial_state[1], "curr_bal": curr_total[0]},
|
||||
)
|
||||
|
||||
|
||||
@@ -12,16 +12,16 @@ except Exception as e:
|
||||
cur = con.cursor()
|
||||
|
||||
# drop accounts table if it exists and create a new table
|
||||
cur.execute(f'''
|
||||
cur.execute("""
|
||||
DROP TABLE IF EXISTS accounts;
|
||||
''')
|
||||
""")
|
||||
|
||||
cur.execute(f'''
|
||||
cur.execute("""
|
||||
CREATE TABLE accounts (
|
||||
account_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
balance REAL NOT NULL DEFAULT 0.0
|
||||
);
|
||||
''')
|
||||
""")
|
||||
|
||||
# randomly create up to 100 accounts with a balance up to 1e9
|
||||
total = 0
|
||||
@@ -29,24 +29,24 @@ num_accts = get_random() % 100 + 1
|
||||
for i in range(num_accts):
|
||||
bal = get_random() % 1e9
|
||||
total += bal
|
||||
cur.execute(f'''
|
||||
cur.execute(f"""
|
||||
INSERT INTO accounts (balance)
|
||||
VALUES ({bal})
|
||||
''')
|
||||
""")
|
||||
|
||||
# drop initial_state table if it exists and create a new table
|
||||
cur.execute(f'''
|
||||
cur.execute("""
|
||||
DROP TABLE IF EXISTS initial_state;
|
||||
''')
|
||||
cur.execute(f'''
|
||||
""")
|
||||
cur.execute("""
|
||||
CREATE TABLE initial_state (
|
||||
num_accts INTEGER,
|
||||
total REAL
|
||||
);
|
||||
''')
|
||||
""")
|
||||
|
||||
# store initial state in the table
|
||||
cur.execute(f'''
|
||||
cur.execute(f"""
|
||||
INSERT INTO initial_state (num_accts, total)
|
||||
VALUES ({num_accts}, {total})
|
||||
''')
|
||||
""")
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import limbo
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
import limbo
|
||||
from antithesis.random import get_random
|
||||
|
||||
handler = RotatingFileHandler(filename='bank_test.log', mode='a', maxBytes=1*1024*1024, backupCount=5, encoding=None, delay=0)
|
||||
handler = RotatingFileHandler(
|
||||
filename="bank_test.log", mode="a", maxBytes=1 * 1024 * 1024, backupCount=5, encoding=None, delay=0
|
||||
)
|
||||
handler.setLevel(logging.INFO)
|
||||
|
||||
logger = logging.getLogger('root')
|
||||
logger = logging.getLogger("root")
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
logger.addHandler(handler)
|
||||
@@ -23,6 +26,7 @@ cur = con.cursor()
|
||||
|
||||
length = cur.execute("SELECT num_accts FROM initial_state").fetchone()[0]
|
||||
|
||||
|
||||
def transaction():
|
||||
# check that sender and recipient are different
|
||||
sender = get_random() % length + 1
|
||||
@@ -34,23 +38,24 @@ def transaction():
|
||||
logger.info(f"Sender ID: {sender} | Recipient ID: {recipient} | Txn Val: {value}")
|
||||
|
||||
cur.execute("BEGIN TRANSACTION;")
|
||||
|
||||
|
||||
# subtract value from balance of the sender account
|
||||
cur.execute(f'''
|
||||
UPDATE accounts
|
||||
cur.execute(f"""
|
||||
UPDATE accounts
|
||||
SET balance = balance - {value}
|
||||
WHERE account_id = {sender};
|
||||
''')
|
||||
""")
|
||||
|
||||
# add value to balance of the recipient account
|
||||
cur.execute(f'''
|
||||
UPDATE accounts
|
||||
cur.execute(f"""
|
||||
UPDATE accounts
|
||||
SET balance = balance + {value}
|
||||
WHERE account_id = {recipient};
|
||||
''')
|
||||
""")
|
||||
|
||||
cur.execute("COMMIT;")
|
||||
|
||||
|
||||
# run up to 100 transactions
|
||||
iterations = get_random() % 100
|
||||
# logger.info(f"Starting {iterations} iterations")
|
||||
|
||||
12
antithesis-tests/pyproject.toml
Normal file
12
antithesis-tests/pyproject.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
dependencies = [
|
||||
"antithesis>=0.1.17",
|
||||
"pylimbo"
|
||||
]
|
||||
description = "Add your description here"
|
||||
name = "antithesis-tests"
|
||||
requires-python = ">=3.13"
|
||||
version = "0.1.0"
|
||||
|
||||
[tool.uv.sources]
|
||||
pylimbo = { workspace = true }
|
||||
@@ -1,22 +1,23 @@
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import json
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
|
||||
import limbo
|
||||
from antithesis.random import get_random, random_choice
|
||||
|
||||
constraints = ['NOT NULL', '']
|
||||
data_type = ['INTEGER', 'REAL', 'TEXT', 'BLOB', 'NUMERIC']
|
||||
constraints = ["NOT NULL", ""]
|
||||
data_type = ["INTEGER", "REAL", "TEXT", "BLOB", "NUMERIC"]
|
||||
|
||||
# remove any existing db files
|
||||
for f in glob.glob('*.db'):
|
||||
for f in glob.glob("*.db"):
|
||||
try:
|
||||
os.remove(f)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
for f in glob.glob('*.db-wal'):
|
||||
for f in glob.glob("*.db-wal"):
|
||||
try:
|
||||
os.remove(f)
|
||||
except OSError:
|
||||
@@ -24,17 +25,17 @@ for f in glob.glob('*.db-wal'):
|
||||
|
||||
# store initial states in a separate db
|
||||
try:
|
||||
con_init = limbo.connect('init_state.db')
|
||||
con_init = limbo.connect("init_state.db")
|
||||
except Exception as e:
|
||||
print(f"Error connecting to database: {e}")
|
||||
exit(0)
|
||||
|
||||
cur_init = con_init.cursor()
|
||||
cur_init.execute('CREATE TABLE schemas (schema TEXT, tbl INT)')
|
||||
cur_init.execute('CREATE TABLE tables (count INT)')
|
||||
cur_init.execute("CREATE TABLE schemas (schema TEXT, tbl INT)")
|
||||
cur_init.execute("CREATE TABLE tables (count INT)")
|
||||
|
||||
try:
|
||||
con = limbo.connect('stress_composer.db')
|
||||
con = limbo.connect("stress_composer.db")
|
||||
except Exception as e:
|
||||
print(f"Error connecting to database: {e}")
|
||||
exit(0)
|
||||
@@ -43,43 +44,43 @@ cur = con.cursor()
|
||||
|
||||
tbl_count = max(1, get_random() % 10)
|
||||
|
||||
cur_init.execute(f'INSERT INTO tables (count) VALUES ({tbl_count})')
|
||||
cur_init.execute(f"INSERT INTO tables (count) VALUES ({tbl_count})")
|
||||
|
||||
schemas = []
|
||||
for i in range(tbl_count):
|
||||
col_count = max(1, get_random() % 10)
|
||||
pk = get_random() % col_count
|
||||
|
||||
schema = {
|
||||
'table': i,
|
||||
'colCount': col_count,
|
||||
'pk': pk
|
||||
}
|
||||
schema = {"table": i, "colCount": col_count, "pk": pk}
|
||||
|
||||
cols = []
|
||||
cols_str = ''
|
||||
cols_str = ""
|
||||
for j in range(col_count):
|
||||
col_data_type = random_choice(data_type)
|
||||
col_constraint_1 = random_choice(constraints)
|
||||
col_constraint_2 = random_choice(constraints)
|
||||
|
||||
col = f'col_{j} {col_data_type} {col_constraint_1} {col_constraint_2 if col_constraint_2 != col_constraint_1 else ""}' if j != pk else f'col_{j} {col_data_type}'
|
||||
col = (
|
||||
f"col_{j} {col_data_type} {col_constraint_1} {col_constraint_2 if col_constraint_2 != col_constraint_1 else ''}" # noqa: E501
|
||||
if j != pk
|
||||
else f"col_{j} {col_data_type}"
|
||||
)
|
||||
|
||||
cols.append(col)
|
||||
|
||||
schema[f'col_{j}'] = {
|
||||
'data_type': col_data_type,
|
||||
'constraint1': col_constraint_1 if j != pk else '',
|
||||
'constraint2': col_constraint_2 if col_constraint_1 != col_constraint_2 else "" if j != pk else 'NOT NULL',
|
||||
schema[f"col_{j}"] = {
|
||||
"data_type": col_data_type,
|
||||
"constraint1": col_constraint_1 if j != pk else "",
|
||||
"constraint2": col_constraint_2 if col_constraint_1 != col_constraint_2 else "" if j != pk else "NOT NULL",
|
||||
}
|
||||
|
||||
cols_str = ', '.join(cols)
|
||||
|
||||
schemas.append(schema)
|
||||
cols_str = ", ".join(cols)
|
||||
|
||||
schemas.append(schema)
|
||||
cur_init.execute(f"INSERT INTO schemas (schema, tbl) VALUES ('{json.dumps(schema)}', {i})")
|
||||
|
||||
cur.execute(f'''
|
||||
cur.execute(f"""
|
||||
CREATE TABLE tbl_{i} ({cols_str})
|
||||
''')
|
||||
""")
|
||||
|
||||
print(f'DB Schemas\n------------\n{json.dumps(schemas, indent=2)}')
|
||||
print(f"DB Schemas\n------------\n{json.dumps(schemas, indent=2)}")
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import json
|
||||
|
||||
import limbo
|
||||
from utils import generate_random_value
|
||||
from antithesis.random import get_random
|
||||
from utils import generate_random_value
|
||||
|
||||
# Get initial state
|
||||
try:
|
||||
con_init = limbo.connect('init_state.db')
|
||||
con_init = limbo.connect("init_state.db")
|
||||
except Exception as e:
|
||||
print(f"Error connecting to database: {e}")
|
||||
exit(0)
|
||||
|
||||
cur_init = con_init.cursor()
|
||||
|
||||
tbl_len = cur_init.execute('SELECT count FROM tables').fetchone()[0]
|
||||
tbl_len = cur_init.execute("SELECT count FROM tables").fetchone()[0]
|
||||
selected_tbl = get_random() % tbl_len
|
||||
tbl_schema = json.loads(cur_init.execute(f'SELECT schema FROM schemas WHERE tbl = {selected_tbl}').fetchone()[0])
|
||||
tbl_schema = json.loads(cur_init.execute(f"SELECT schema FROM schemas WHERE tbl = {selected_tbl}").fetchone()[0])
|
||||
|
||||
# get primary key column
|
||||
pk = tbl_schema['pk']
|
||||
pk = tbl_schema["pk"]
|
||||
# get non-pk columns
|
||||
cols = [f'col_{col}' for col in range(tbl_schema['colCount']) if col != pk]
|
||||
cols = [f"col_{col}" for col in range(tbl_schema["colCount"]) if col != pk]
|
||||
|
||||
try:
|
||||
con = limbo.connect('stress_composer.db')
|
||||
con = limbo.connect("stress_composer.db")
|
||||
except limbo.OperationalError as e:
|
||||
print(f'Failed to open stress_composer.db. Exiting... {e}')
|
||||
print(f"Failed to open stress_composer.db. Exiting... {e}")
|
||||
exit(0)
|
||||
cur = con.cursor()
|
||||
|
||||
deletions = get_random() % 100
|
||||
print(f'Attempt to delete {deletions} rows in tbl_{selected_tbl}...')
|
||||
print(f"Attempt to delete {deletions} rows in tbl_{selected_tbl}...")
|
||||
|
||||
for i in range(deletions):
|
||||
where_clause = f"col_{pk} = {generate_random_value(tbl_schema[f'col_{pk}']['data_type'])}"
|
||||
|
||||
cur.execute(f'''
|
||||
cur.execute(f"""
|
||||
DELETE FROM tbl_{selected_tbl} WHERE {where_clause}
|
||||
''')
|
||||
|
||||
""")
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import json
|
||||
import limbo
|
||||
from utils import generate_random_value
|
||||
from antithesis.random import get_random
|
||||
|
||||
import limbo
|
||||
from antithesis.random import get_random
|
||||
from utils import generate_random_value
|
||||
|
||||
# Get initial state
|
||||
try:
|
||||
con_init = limbo.connect('init_state.db')
|
||||
con_init = limbo.connect("init_state.db")
|
||||
except Exception as e:
|
||||
print(f"Error connecting to database: {e}")
|
||||
exit(0)
|
||||
|
||||
cur_init = con_init.cursor()
|
||||
|
||||
tbl_len = cur_init.execute('SELECT count FROM tables').fetchone()[0]
|
||||
tbl_len = cur_init.execute("SELECT count FROM tables").fetchone()[0]
|
||||
selected_tbl = get_random() % tbl_len
|
||||
tbl_schema = json.loads(cur_init.execute(f'SELECT schema FROM schemas WHERE tbl = {selected_tbl}').fetchone()[0])
|
||||
cols = ', '.join([f'col_{col}' for col in range(tbl_schema['colCount'])])
|
||||
tbl_schema = json.loads(cur_init.execute(f"SELECT schema FROM schemas WHERE tbl = {selected_tbl}").fetchone()[0])
|
||||
cols = ", ".join([f"col_{col}" for col in range(tbl_schema["colCount"])])
|
||||
|
||||
try:
|
||||
con = limbo.connect('stress_composer.db')
|
||||
con = limbo.connect("stress_composer.db")
|
||||
except limbo.OperationalError as e:
|
||||
print(f'Failed to open stress_composer.db. Exiting... {e}')
|
||||
print(f"Failed to open stress_composer.db. Exiting... {e}")
|
||||
exit(0)
|
||||
|
||||
cur = con.cursor()
|
||||
|
||||
# insert up to 100 rows in the selected table
|
||||
insertions = get_random() % 100
|
||||
print(f'Inserting {insertions} rows...')
|
||||
print(f"Inserting {insertions} rows...")
|
||||
|
||||
for i in range(insertions):
|
||||
values = [generate_random_value(tbl_schema[f'col_{col}']['data_type']) for col in range(tbl_schema['colCount'])]
|
||||
values = [generate_random_value(tbl_schema[f"col_{col}"]["data_type"]) for col in range(tbl_schema["colCount"])]
|
||||
try:
|
||||
cur.execute(f'''
|
||||
cur.execute(f"""
|
||||
INSERT INTO tbl_{selected_tbl} ({cols})
|
||||
VALUES ({', '.join(values)})
|
||||
''')
|
||||
VALUES ({", ".join(values)})
|
||||
""")
|
||||
except limbo.OperationalError as e:
|
||||
if "UNIQUE constraint failed" in str(e):
|
||||
# Ignore UNIQUE constraint violations
|
||||
@@ -46,4 +46,3 @@ for i in range(insertions):
|
||||
else:
|
||||
# Re-raise other operational errors
|
||||
raise
|
||||
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import json
|
||||
|
||||
import limbo
|
||||
from antithesis.random import get_random
|
||||
from antithesis.assertions import always
|
||||
from antithesis.random import get_random
|
||||
|
||||
# Get initial state
|
||||
try:
|
||||
con_init = limbo.connect('init_state.db')
|
||||
con_init = limbo.connect("init_state.db")
|
||||
except Exception as e:
|
||||
print(f"Error connecting to database: {e}")
|
||||
exit(0)
|
||||
|
||||
cur_init = con_init.cursor()
|
||||
|
||||
tbl_len = cur_init.execute('SELECT count FROM tables').fetchone()[0]
|
||||
tbl_len = cur_init.execute("SELECT count FROM tables").fetchone()[0]
|
||||
selected_tbl = get_random() % tbl_len
|
||||
tbl_schema = json.loads(cur_init.execute(f'SELECT schema FROM schemas WHERE tbl = {selected_tbl}').fetchone()[0])
|
||||
cols = ', '.join([f'col_{col}' for col in range(tbl_schema['colCount'])])
|
||||
tbl_schema = json.loads(cur_init.execute(f"SELECT schema FROM schemas WHERE tbl = {selected_tbl}").fetchone()[0])
|
||||
cols = ", ".join([f"col_{col}" for col in range(tbl_schema["colCount"])])
|
||||
|
||||
try:
|
||||
con = limbo.connect('stress_composer.db')
|
||||
con = limbo.connect("stress_composer.db")
|
||||
except limbo.OperationalError as e:
|
||||
print(f'Failed to open stress_composer.db. Exiting... {e}')
|
||||
print(f"Failed to open stress_composer.db. Exiting... {e}")
|
||||
exit(0)
|
||||
cur = con.cursor();
|
||||
cur = con.cursor()
|
||||
|
||||
print('Running integrity check...')
|
||||
print("Running integrity check...")
|
||||
|
||||
result = cur.execute("PRAGMA integrity_check")
|
||||
row = result.fetchone()
|
||||
always(row == ("ok",), f"Integrity check failed: {row}", {})
|
||||
always(row == ("ok",), f"Integrity check failed: {row}", {})
|
||||
|
||||
@@ -1,57 +1,58 @@
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import json
|
||||
|
||||
import limbo
|
||||
from utils import generate_random_value
|
||||
from antithesis.random import get_random
|
||||
from utils import generate_random_value
|
||||
|
||||
# Get initial state
|
||||
try:
|
||||
con_init = limbo.connect('init_state.db')
|
||||
con_init = limbo.connect("init_state.db")
|
||||
except Exception as e:
|
||||
print(f"Error connecting to database: {e}")
|
||||
exit(0)
|
||||
|
||||
cur_init = con_init.cursor()
|
||||
|
||||
tbl_len = cur_init.execute('SELECT count FROM tables').fetchone()[0]
|
||||
tbl_len = cur_init.execute("SELECT count FROM tables").fetchone()[0]
|
||||
selected_tbl = get_random() % tbl_len
|
||||
tbl_schema = json.loads(cur_init.execute(f'SELECT schema FROM schemas WHERE tbl = {selected_tbl}').fetchone()[0])
|
||||
tbl_schema = json.loads(cur_init.execute(f"SELECT schema FROM schemas WHERE tbl = {selected_tbl}").fetchone()[0])
|
||||
|
||||
# get primary key column
|
||||
pk = tbl_schema['pk']
|
||||
pk = tbl_schema["pk"]
|
||||
# get non-pk columns
|
||||
cols = [f'col_{col}' for col in range(tbl_schema['colCount']) if col != pk]
|
||||
cols = [f"col_{col}" for col in range(tbl_schema["colCount"]) if col != pk]
|
||||
# print(cols)
|
||||
try:
|
||||
con = limbo.connect('stress_composer.db')
|
||||
con = limbo.connect("stress_composer.db")
|
||||
except limbo.OperationalError as e:
|
||||
print(f'Failed to open stress_composer.db. Exiting... {e}')
|
||||
print(f"Failed to open stress_composer.db. Exiting... {e}")
|
||||
exit(0)
|
||||
cur = con.cursor()
|
||||
|
||||
# insert up to 100 rows in the selected table
|
||||
updates = get_random() % 100
|
||||
print(f'Attempt to update {updates} rows in tbl_{selected_tbl}...')
|
||||
print(f"Attempt to update {updates} rows in tbl_{selected_tbl}...")
|
||||
|
||||
for i in range(updates):
|
||||
set_clause = ''
|
||||
if tbl_schema['colCount'] == 1:
|
||||
set_clause = ""
|
||||
if tbl_schema["colCount"] == 1:
|
||||
set_clause = f"col_{pk} = {generate_random_value(tbl_schema[f'col_{pk}']['data_type'])}"
|
||||
else:
|
||||
values = []
|
||||
for col in cols:
|
||||
# print(col)
|
||||
values.append(f"{col} = {generate_random_value(tbl_schema[col]['data_type'])}")
|
||||
set_clause = ', '.join(values)
|
||||
set_clause = ", ".join(values)
|
||||
|
||||
where_clause = f"col_{pk} = {generate_random_value(tbl_schema[f'col_{pk}']['data_type'])}"
|
||||
# print(where_clause)
|
||||
|
||||
try:
|
||||
cur.execute(f'''
|
||||
cur.execute(f"""
|
||||
UPDATE tbl_{selected_tbl} SET {set_clause} WHERE {where_clause}
|
||||
''')
|
||||
""")
|
||||
except limbo.OperationalError as e:
|
||||
if "UNIQUE constraint failed" in str(e):
|
||||
# Ignore UNIQUE constraint violations
|
||||
@@ -59,4 +60,3 @@ for i in range(updates):
|
||||
else:
|
||||
# Re-raise other operational errors
|
||||
raise
|
||||
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import string
|
||||
|
||||
from antithesis.random import get_random, random_choice
|
||||
|
||||
|
||||
def generate_random_identifier(type: str, num: int):
|
||||
return ''.join(type, '_', get_random() % num)
|
||||
return "".join(type, "_", get_random() % num)
|
||||
|
||||
|
||||
def generate_random_value(type_str):
|
||||
if type_str == 'INTEGER':
|
||||
if type_str == "INTEGER":
|
||||
return str(get_random() % 100)
|
||||
elif type_str == 'REAL':
|
||||
return '{:.2f}'.format(get_random() % 100 / 100.0)
|
||||
elif type_str == 'TEXT':
|
||||
elif type_str == "REAL":
|
||||
return "{:.2f}".format(get_random() % 100 / 100.0)
|
||||
elif type_str == "TEXT":
|
||||
return f"'{''.join(random_choice(string.ascii_lowercase) for _ in range(5))}'"
|
||||
elif type_str == 'BLOB':
|
||||
elif type_str == "BLOB":
|
||||
return f"x'{''.join(random_choice(string.ascii_lowercase) for _ in range(5)).encode().hex()}'"
|
||||
elif type_str == 'NUMERIC':
|
||||
elif type_str == "NUMERIC":
|
||||
return str(get_random() % 100)
|
||||
else:
|
||||
return NULL
|
||||
return "NULL"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
REQUIREMENTS := requirements.txt
|
||||
REQUIREMENTS_DEV := requirements-dev.txt
|
||||
|
||||
all: check-requirements install lint test
|
||||
.PHONY: all
|
||||
|
||||
install:
|
||||
@echo "Installing requirements..."
|
||||
pip install -r requirements.txt -r requirements-dev.txt
|
||||
.PHONY: install
|
||||
|
||||
test:
|
||||
@echo "Running tests..."
|
||||
pytest
|
||||
.PHONY: test
|
||||
|
||||
lint:
|
||||
@echo "Running linters..."
|
||||
ruff check
|
||||
ruff format --diff
|
||||
.PHONY: lint
|
||||
|
||||
check-requirements:
|
||||
@echo "Checking requirements files..."
|
||||
mkdir -p .tmp
|
||||
pip-compile pyproject.toml --quiet --output-file=.tmp/$(REQUIREMENTS)
|
||||
pip-compile pyproject.toml --quiet --extra=dev --output-file=.tmp/$(REQUIREMENTS_DEV)
|
||||
diff -u $(REQUIREMENTS) .tmp/$(REQUIREMENTS) || (echo "$(REQUIREMENTS) doesn't match pyproject.toml" && exit 1)
|
||||
diff -u $(REQUIREMENTS_DEV) .tmp/$(REQUIREMENTS_DEV) || (echo "$(REQUIREMENTS_DEV) doesn't match pyproject.toml" && exit 1)
|
||||
@echo "Requirements files match pyproject.toml"
|
||||
.PHONY: check-requirements
|
||||
|
||||
compile-requirements:
|
||||
@echo "Compiling requirements files..."
|
||||
pip-compile pyproject.toml --output-file=$(REQUIREMENTS)
|
||||
pip-compile pyproject.toml --extra=dev --output-file=$(REQUIREMENTS_DEV)
|
||||
.PHONY: compile-requirements
|
||||
@@ -53,19 +53,6 @@ strip-extras = true
|
||||
header = false
|
||||
upgrade = false
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
|
||||
[tool.ruff.lint]
|
||||
extend-select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warings
|
||||
"F", # pyflakes
|
||||
'Q', # flake8-quotes
|
||||
'C90', # mccabe
|
||||
'I', # isort
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = 'tests'
|
||||
log_format = '%(name)s %(levelname)s: %(message)s'
|
||||
@@ -82,3 +69,17 @@ exclude_lines = [
|
||||
'if TYPE_CHECKING:',
|
||||
'@overload',
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"coverage>=7.6.1",
|
||||
"iniconfig>=2.1.0",
|
||||
"maturin>=1.7.8",
|
||||
"mypy>=1.11.0",
|
||||
"mypy-extensions>=1.1.0",
|
||||
"pluggy>=1.6.0",
|
||||
"pytest>=8.3.1",
|
||||
"pytest-cov>=5.0.0",
|
||||
"ruff>=0.5.4",
|
||||
"typing-extensions>=4.13.0",
|
||||
]
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
import limbo
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
import argparse
|
||||
import sqlite3
|
||||
|
||||
from faker import Faker
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filename')
|
||||
parser.add_argument('-c', '--count', type=int)
|
||||
parser.add_argument("filename")
|
||||
parser.add_argument("-c", "--count", type=int)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -14,7 +15,7 @@ conn = sqlite3.connect(args.filename)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create the user table
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
id INTEGER PRIMARY KEY,
|
||||
first_name TEXT,
|
||||
@@ -26,7 +27,7 @@ cursor.execute('''
|
||||
state TEXT,
|
||||
zipcode TEXT
|
||||
)
|
||||
''')
|
||||
""")
|
||||
|
||||
fake = Faker()
|
||||
for _ in range(args.count):
|
||||
@@ -39,10 +40,13 @@ for _ in range(args.count):
|
||||
state = fake.state_abbr()
|
||||
zipcode = fake.zipcode()
|
||||
|
||||
cursor.execute('''
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO user (first_name, last_name, email, phone_number, address, city, state, zipcode)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (first_name, last_name, email, phone_number, address, city, state, zipcode))
|
||||
""",
|
||||
(first_name, last_name, email, phone_number, address, city, state, zipcode),
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib
|
||||
import csv
|
||||
|
||||
font = {'family' : 'normal',
|
||||
'weight' : 'bold',
|
||||
'size' : 22}
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
matplotlib.rcParams.update({'font.size': 22})
|
||||
font = {"family": "normal", "weight": "bold", "size": 22}
|
||||
|
||||
file_name = 'results.csv'
|
||||
matplotlib.rcParams.update({"font.size": 22})
|
||||
|
||||
file_name = "results.csv"
|
||||
threads = []
|
||||
p50_values = []
|
||||
p95_values = []
|
||||
@@ -22,34 +21,34 @@ p99_limbo = []
|
||||
p999_limbo = []
|
||||
|
||||
# Parse the CSV file
|
||||
with open(file_name, 'r') as csvfile:
|
||||
with open(file_name, "r") as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
for row in reader:
|
||||
if row['system'] == 'rusqlite':
|
||||
threads.append(int(row['count']))
|
||||
p50_values.append(float(row['p50']) / 1e3)
|
||||
p95_values.append(float(row['p95']) / 1e3)
|
||||
p99_values.append(float(row['p99']) / 1e3)
|
||||
p999_values.append(float(row['p999']) / 1e3)
|
||||
if row["system"] == "rusqlite":
|
||||
threads.append(int(row["count"]))
|
||||
p50_values.append(float(row["p50"]) / 1e3)
|
||||
p95_values.append(float(row["p95"]) / 1e3)
|
||||
p99_values.append(float(row["p99"]) / 1e3)
|
||||
p999_values.append(float(row["p999"]) / 1e3)
|
||||
else:
|
||||
p95_limbo.append(float(row['p95']) / 1e3)
|
||||
p99_limbo.append(float(row['p99']) / 1e3)
|
||||
p999_limbo.append(float(row['p999']) / 1e3)
|
||||
p95_limbo.append(float(row["p95"]) / 1e3)
|
||||
p99_limbo.append(float(row["p99"]) / 1e3)
|
||||
p999_limbo.append(float(row["p999"]) / 1e3)
|
||||
|
||||
plt.figure(figsize=(10, 6))
|
||||
plt.plot(threads, p999_values, label='rusqlite (p999)', linestyle='solid', marker='$\u2217$')
|
||||
plt.plot(threads, p999_limbo, label='limbo (p999)', linestyle='solid', marker='$\u2217$')
|
||||
plt.plot(threads, p99_values, label='rusqlite (p99)', linestyle='solid', marker='$\u002B$')
|
||||
plt.plot(threads, p99_limbo, label='limbo (p99)', linestyle='solid', marker='$\u002B$')
|
||||
#plt.plot(threads, p95_values, label='p95', linestyle='solid', marker="$\u25FE$")
|
||||
#plt.plot(threads, p50_values, label='p50', linestyle='solid', marker="$\u25B2$")
|
||||
plt.plot(threads, p999_values, label="rusqlite (p999)", linestyle="solid", marker="$\u2217$")
|
||||
plt.plot(threads, p999_limbo, label="limbo (p999)", linestyle="solid", marker="$\u2217$")
|
||||
plt.plot(threads, p99_values, label="rusqlite (p99)", linestyle="solid", marker="$\u002b$")
|
||||
plt.plot(threads, p99_limbo, label="limbo (p99)", linestyle="solid", marker="$\u002b$")
|
||||
# plt.plot(threads, p95_values, label='p95', linestyle='solid', marker="$\u25FE$")
|
||||
# plt.plot(threads, p50_values, label='p50', linestyle='solid', marker="$\u25B2$")
|
||||
|
||||
plt.yscale("log")
|
||||
plt.xlabel('Number of Tenants')
|
||||
plt.ylabel('Latency (µs)')
|
||||
plt.xlabel("Number of Tenants")
|
||||
plt.ylabel("Latency (µs)")
|
||||
plt.grid(True)
|
||||
|
||||
plt.legend()
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('latency_distribution.pdf')
|
||||
plt.savefig("latency_distribution.pdf")
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
import argparse
|
||||
import sqlite3
|
||||
|
||||
from faker import Faker
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filename')
|
||||
parser.add_argument('-c', '--count', type=int)
|
||||
parser.add_argument("filename")
|
||||
parser.add_argument("-c", "--count", type=int)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -14,7 +15,7 @@ conn = sqlite3.connect(args.filename)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create the user table
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
id INTEGER PRIMARY KEY,
|
||||
first_name TEXT,
|
||||
@@ -26,7 +27,7 @@ cursor.execute('''
|
||||
state TEXT,
|
||||
zipcode TEXT
|
||||
)
|
||||
''')
|
||||
""")
|
||||
|
||||
fake = Faker()
|
||||
for _ in range(args.count):
|
||||
@@ -39,10 +40,13 @@ for _ in range(args.count):
|
||||
state = fake.state_abbr()
|
||||
zipcode = fake.zipcode()
|
||||
|
||||
cursor.execute('''
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO user (first_name, last_name, email, phone_number, address, city, state, zipcode)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (first_name, last_name, email, phone_number, address, city, state, zipcode))
|
||||
""",
|
||||
(first_name, last_name, email, phone_number, address, city, state, zipcode),
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib
|
||||
import csv
|
||||
|
||||
font = {'family' : 'normal',
|
||||
'weight' : 'bold',
|
||||
'size' : 22}
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
matplotlib.rcParams.update({'font.size': 22})
|
||||
font = {"family": "normal", "weight": "bold", "size": 22}
|
||||
|
||||
file_name = 'results.csv'
|
||||
matplotlib.rcParams.update({"font.size": 22})
|
||||
|
||||
file_name = "results.csv"
|
||||
threads = []
|
||||
p50_values = []
|
||||
p95_values = []
|
||||
@@ -18,27 +17,27 @@ p99_values = []
|
||||
p999_values = []
|
||||
|
||||
# Parse the CSV file
|
||||
with open(file_name, 'r') as csvfile:
|
||||
with open(file_name, "r") as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
for row in reader:
|
||||
threads.append(int(row['count']))
|
||||
p50_values.append(float(row['p50']) / 1e3)
|
||||
p95_values.append(float(row['p95']) / 1e3)
|
||||
p99_values.append(float(row['p99']) / 1e3)
|
||||
p999_values.append(float(row['p999']) / 1e3)
|
||||
threads.append(int(row["count"]))
|
||||
p50_values.append(float(row["p50"]) / 1e3)
|
||||
p95_values.append(float(row["p95"]) / 1e3)
|
||||
p99_values.append(float(row["p99"]) / 1e3)
|
||||
p999_values.append(float(row["p999"]) / 1e3)
|
||||
|
||||
plt.figure(figsize=(10, 6))
|
||||
plt.plot(threads, p999_values, label='p999', linestyle='solid', marker='$\u2217$')
|
||||
plt.plot(threads, p99_values, label='p99', linestyle='solid', marker='$\u002B$')
|
||||
plt.plot(threads, p95_values, label='p95', linestyle='solid', marker="$\u25FE$")
|
||||
plt.plot(threads, p50_values, label='p50', linestyle='solid', marker="$\u25B2$")
|
||||
plt.plot(threads, p999_values, label="p999", linestyle="solid", marker="$\u2217$")
|
||||
plt.plot(threads, p99_values, label="p99", linestyle="solid", marker="$\u002b$")
|
||||
plt.plot(threads, p95_values, label="p95", linestyle="solid", marker="$\u25fe$")
|
||||
plt.plot(threads, p50_values, label="p50", linestyle="solid", marker="$\u25b2$")
|
||||
|
||||
plt.yscale("log")
|
||||
plt.xlabel('Number of Threads')
|
||||
plt.ylabel('Latency (µs)')
|
||||
plt.xlabel("Number of Threads")
|
||||
plt.ylabel("Latency (µs)")
|
||||
plt.grid(True)
|
||||
|
||||
plt.legend()
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('latency_distribution.pdf')
|
||||
plt.savefig("latency_distribution.pdf")
|
||||
|
||||
@@ -14,4 +14,17 @@ package = false
|
||||
limbo_test = { workspace = true }
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["testing", "scripts"]
|
||||
members = ["testing", "scripts", "antithesis-tests", "bindings/python"]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
|
||||
[tool.ruff.lint]
|
||||
extend-select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warings
|
||||
"F", # pyflakes
|
||||
'Q', # flake8-quotes
|
||||
'C90', # mccabe
|
||||
'I', # isort
|
||||
]
|
||||
|
||||
@@ -9,29 +9,33 @@
|
||||
# ```
|
||||
# pip install PyGithub
|
||||
# ```
|
||||
import sys
|
||||
import re
|
||||
from github import Github
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
import json
|
||||
|
||||
from github import Github
|
||||
|
||||
|
||||
def run_command(command):
|
||||
process = subprocess.Popen(
|
||||
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
output, error = process.communicate()
|
||||
return output.decode('utf-8').strip(), error.decode('utf-8').strip(), process.returncode
|
||||
return output.decode("utf-8").strip(), error.decode("utf-8").strip(), process.returncode
|
||||
|
||||
def load_user_mapping(file_path='.github.json'):
|
||||
|
||||
def load_user_mapping(file_path=".github.json"):
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r') as f:
|
||||
with open(file_path, "r") as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
|
||||
user_mapping = load_user_mapping()
|
||||
|
||||
|
||||
def get_user_email(g, username):
|
||||
if username in user_mapping:
|
||||
return f"{user_mapping[username]['name']} <{user_mapping[username]['email']}>"
|
||||
@@ -48,6 +52,7 @@ def get_user_email(g, username):
|
||||
# If we couldn't find an email, return a noreply address
|
||||
return f"{username} <{username}@users.noreply.github.com>"
|
||||
|
||||
|
||||
def get_pr_info(g, repo, pr_number):
|
||||
pr = repo.get_pull(int(pr_number))
|
||||
author = pr.user
|
||||
@@ -57,41 +62,43 @@ def get_pr_info(g, repo, pr_number):
|
||||
reviewed_by = []
|
||||
reviews = pr.get_reviews()
|
||||
for review in reviews:
|
||||
if review.state == 'APPROVED':
|
||||
if review.state == "APPROVED":
|
||||
reviewer = review.user
|
||||
reviewed_by.append(get_user_email(g, reviewer.login))
|
||||
|
||||
return {
|
||||
'number': pr.number,
|
||||
'title': pr.title,
|
||||
'author': author_name,
|
||||
'head': pr.head.ref,
|
||||
'head_sha': pr.head.sha,
|
||||
'body': pr.body.strip() if pr.body else '',
|
||||
'reviewed_by': reviewed_by
|
||||
"number": pr.number,
|
||||
"title": pr.title,
|
||||
"author": author_name,
|
||||
"head": pr.head.ref,
|
||||
"head_sha": pr.head.sha,
|
||||
"body": pr.body.strip() if pr.body else "",
|
||||
"reviewed_by": reviewed_by,
|
||||
}
|
||||
|
||||
|
||||
def wrap_text(text, width=72):
|
||||
lines = text.split('\n')
|
||||
lines = text.split("\n")
|
||||
wrapped_lines = []
|
||||
in_code_block = False
|
||||
for line in lines:
|
||||
if line.strip().startswith('```'):
|
||||
if line.strip().startswith("```"):
|
||||
in_code_block = not in_code_block
|
||||
wrapped_lines.append(line)
|
||||
elif in_code_block:
|
||||
wrapped_lines.append(line)
|
||||
else:
|
||||
wrapped_lines.extend(textwrap.wrap(line, width=width))
|
||||
return '\n'.join(wrapped_lines)
|
||||
return "\n".join(wrapped_lines)
|
||||
|
||||
|
||||
def merge_pr(pr_number):
|
||||
# GitHub authentication
|
||||
token = os.getenv('GITHUB_TOKEN')
|
||||
token = os.getenv("GITHUB_TOKEN")
|
||||
g = Github(token)
|
||||
|
||||
# Get the repository
|
||||
repo_name = os.getenv('GITHUB_REPOSITORY')
|
||||
repo_name = os.getenv("GITHUB_REPOSITORY")
|
||||
if not repo_name:
|
||||
print("Error: GITHUB_REPOSITORY environment variable not set")
|
||||
sys.exit(1)
|
||||
@@ -102,19 +109,19 @@ def merge_pr(pr_number):
|
||||
|
||||
# Format commit message
|
||||
commit_title = f"Merge '{pr_info['title']}' from {pr_info['author']}"
|
||||
commit_body = wrap_text(pr_info['body'])
|
||||
commit_body = wrap_text(pr_info["body"])
|
||||
|
||||
commit_message = f"{commit_title}\n\n{commit_body}\n"
|
||||
|
||||
# Add Reviewed-by lines
|
||||
for approver in pr_info['reviewed_by']:
|
||||
for approver in pr_info["reviewed_by"]:
|
||||
commit_message += f"\nReviewed-by: {approver}"
|
||||
|
||||
# Add Closes line
|
||||
commit_message += f"\n\nCloses #{pr_info['number']}"
|
||||
|
||||
# Create a temporary file for the commit message
|
||||
with tempfile.NamedTemporaryFile(mode='w', delete=False) as temp_file:
|
||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
|
||||
temp_file.write(commit_message)
|
||||
temp_file_path = temp_file.name
|
||||
|
||||
@@ -147,13 +154,14 @@ def merge_pr(pr_number):
|
||||
# Clean up the temporary file
|
||||
os.unlink(temp_file_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: python merge_pr.py <pr_number>")
|
||||
sys.exit(1)
|
||||
|
||||
pr_number = sys.argv[1]
|
||||
if not re.match(r'^\d+$', pr_number):
|
||||
if not re.match(r"^\d+$", pr_number):
|
||||
print("Error: PR number must be a positive integer")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -6,22 +6,21 @@ updates the JavaScript and WebAssembly bindings package.json and package-lock.js
|
||||
uses cargo update to update Cargo.lock, creates a git commit, and adds a version tag.
|
||||
"""
|
||||
|
||||
import re
|
||||
import argparse
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Define all npm package paths in one place
|
||||
NPM_PACKAGES = [
|
||||
"bindings/javascript",
|
||||
"bindings/javascript/npm/darwin-universal",
|
||||
"bindings/javascript/npm/linux-x64-gnu",
|
||||
"bindings/javascript/npm/linux-x64-gnu",
|
||||
"bindings/javascript/npm/win32-x64-msvc",
|
||||
"bindings/wasm"
|
||||
"bindings/wasm",
|
||||
]
|
||||
|
||||
|
||||
@@ -29,10 +28,7 @@ def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Update version in project files")
|
||||
|
||||
# Version argument
|
||||
parser.add_argument(
|
||||
"version",
|
||||
help="The new version to set (e.g., 0.1.0)"
|
||||
)
|
||||
parser.add_argument("version", help="The new version to set (e.g., 0.1.0)")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
@@ -58,7 +54,7 @@ def update_cargo_toml(new_version):
|
||||
|
||||
# Pattern to match version in various contexts while maintaining the quotes
|
||||
pattern = r'(version\s*=\s*)"' + re.escape(current_version) + r'"'
|
||||
updated_content = re.sub(pattern, fr'\1"{new_version}"', content)
|
||||
updated_content = re.sub(pattern, rf'\1"{new_version}"', content)
|
||||
|
||||
cargo_path.write_text(updated_content)
|
||||
return True
|
||||
@@ -66,7 +62,7 @@ def update_cargo_toml(new_version):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def update_package_json(dir_path, new_version):
|
||||
def update_package_json(dir_path, new_version): # noqa: C901
|
||||
"""Update version in package.json and package-lock.json files."""
|
||||
dir_path = Path(dir_path)
|
||||
|
||||
@@ -77,14 +73,14 @@ def update_package_json(dir_path, new_version):
|
||||
return False
|
||||
|
||||
# Read and parse the package.json file
|
||||
with open(package_path, 'r') as f:
|
||||
with open(package_path, "r") as f:
|
||||
package_data = json.load(f)
|
||||
|
||||
# Update version regardless of current value
|
||||
package_data['version'] = new_version
|
||||
package_data["version"] = new_version
|
||||
|
||||
# Write updated package.json
|
||||
with open(package_path, 'w') as f:
|
||||
with open(package_path, "w") as f:
|
||||
json.dump(package_data, f, indent=2)
|
||||
except Exception:
|
||||
return False
|
||||
@@ -96,27 +92,27 @@ def update_package_json(dir_path, new_version):
|
||||
return True # package.json was updated successfully
|
||||
|
||||
# Read and parse the package-lock.json file
|
||||
with open(lock_path, 'r') as f:
|
||||
with open(lock_path, "r") as f:
|
||||
lock_data = json.load(f)
|
||||
|
||||
# Update version in multiple places in package-lock.json
|
||||
if 'version' in lock_data:
|
||||
lock_data['version'] = new_version
|
||||
if "version" in lock_data:
|
||||
lock_data["version"] = new_version
|
||||
|
||||
# Update version in packages section if it exists (npm >= 7)
|
||||
if 'packages' in lock_data:
|
||||
if '' in lock_data['packages']: # Root package
|
||||
if 'version' in lock_data['packages']['']:
|
||||
lock_data['packages']['']['version'] = new_version
|
||||
if "packages" in lock_data:
|
||||
if "" in lock_data["packages"]: # Root package
|
||||
if "version" in lock_data["packages"][""]:
|
||||
lock_data["packages"][""]["version"] = new_version
|
||||
|
||||
# Update version in dependencies section if it exists (older npm)
|
||||
package_name = package_data.get('name', '')
|
||||
if 'dependencies' in lock_data and package_name in lock_data['dependencies']:
|
||||
if 'version' in lock_data['dependencies'][package_name]:
|
||||
lock_data['dependencies'][package_name]['version'] = new_version
|
||||
package_name = package_data.get("name", "")
|
||||
if "dependencies" in lock_data and package_name in lock_data["dependencies"]:
|
||||
if "version" in lock_data["dependencies"][package_name]:
|
||||
lock_data["dependencies"][package_name]["version"] = new_version
|
||||
|
||||
# Write updated package-lock.json
|
||||
with open(lock_path, 'w') as f:
|
||||
with open(lock_path, "w") as f:
|
||||
json.dump(lock_data, f, indent=2)
|
||||
|
||||
return True
|
||||
@@ -137,10 +133,7 @@ def run_cargo_update():
|
||||
"""Run cargo update to update the Cargo.lock file."""
|
||||
try:
|
||||
# Run cargo update showing its output with verbose flag
|
||||
subprocess.run(
|
||||
["cargo", "update", "--workspace", "--verbose"],
|
||||
check=True
|
||||
)
|
||||
subprocess.run(["cargo", "update", "--workspace", "--verbose"], check=True)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
@@ -156,7 +149,7 @@ def create_git_commit_and_tag(version):
|
||||
for package_path in NPM_PACKAGES:
|
||||
package_json = f"{package_path}/package.json"
|
||||
package_lock = f"{package_path}/package-lock.json"
|
||||
|
||||
|
||||
if os.path.exists(package_json):
|
||||
files_to_add.append(package_json)
|
||||
if os.path.exists(package_lock):
|
||||
@@ -165,26 +158,17 @@ def create_git_commit_and_tag(version):
|
||||
# Add each file individually
|
||||
for file in files_to_add:
|
||||
try:
|
||||
subprocess.run(
|
||||
["git", "add", file],
|
||||
check=True
|
||||
)
|
||||
subprocess.run(["git", "add", file], check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"Warning: Could not add {file} to git")
|
||||
|
||||
# Create commit
|
||||
commit_message = f"Limbo {version}"
|
||||
subprocess.run(
|
||||
["git", "commit", "-m", commit_message],
|
||||
check=True
|
||||
)
|
||||
subprocess.run(["git", "commit", "-m", commit_message], check=True)
|
||||
|
||||
# Create tag
|
||||
tag_name = f"v{version}"
|
||||
subprocess.run(
|
||||
["git", "tag", "-a", tag_name, "-m", f"Version {version}"],
|
||||
check=True
|
||||
)
|
||||
subprocess.run(["git", "tag", "-a", tag_name, "-m", f"Version {version}"], check=True)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from pathlib import Path
|
||||
import time
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from cli_tests import console
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
|
||||
|
||||
def test_basic_queries():
|
||||
@@ -62,7 +63,7 @@ def test_joins():
|
||||
shell.run_test(
|
||||
"file-cross-join",
|
||||
"select * from users, products limit 1;",
|
||||
"1|Jamie|Foster|dylan00@example.com|496-522-9493|62375 Johnson Rest Suite 322|West Lauriestad|IL|35865|94|1|hat|79.0",
|
||||
"1|Jamie|Foster|dylan00@example.com|496-522-9493|62375 Johnson Rest Suite 322|West Lauriestad|IL|35865|94|1|hat|79.0", # noqa: E501
|
||||
)
|
||||
shell.quit()
|
||||
|
||||
@@ -76,7 +77,7 @@ def test_left_join_self():
|
||||
|
||||
shell.run_test(
|
||||
"file-left-join-self",
|
||||
"select u1.first_name as user_name, u2.first_name as neighbor_name from users u1 left join users as u2 on u1.id = u2.id + 1 limit 2;",
|
||||
"select u1.first_name as user_name, u2.first_name as neighbor_name from users u1 left join users as u2 on u1.id = u2.id + 1 limit 2;", # noqa: E501
|
||||
"Jamie|\nCindy|Jamie",
|
||||
)
|
||||
shell.quit()
|
||||
@@ -99,9 +100,7 @@ def test_switch_back_to_in_memory():
|
||||
shell.run_test("open-testing-db-file", ".open testing/testing.db", "")
|
||||
# Then switch back to :memory:
|
||||
shell.run_test("switch-back", ".open :memory:", "")
|
||||
shell.run_test(
|
||||
"schema-in-memory", ".schema users", "-- Error: Table 'users' not found."
|
||||
)
|
||||
shell.run_test("schema-in-memory", ".schema users", "-- Error: Table 'users' not found.")
|
||||
shell.quit()
|
||||
|
||||
|
||||
@@ -172,9 +171,7 @@ SELECT 2;"""
|
||||
def test_comments():
|
||||
shell = TestLimboShell()
|
||||
shell.run_test("single-line-comment", "-- this is a comment\nSELECT 1;", "1")
|
||||
shell.run_test(
|
||||
"multi-line-comments", "-- First comment\n-- Second comment\nSELECT 2;", "2"
|
||||
)
|
||||
shell.run_test("multi-line-comments", "-- First comment\n-- Second comment\nSELECT 2;", "2")
|
||||
shell.run_test("block-comment", "/*\nMulti-line block comment\n*/\nSELECT 3;", "3")
|
||||
shell.run_test(
|
||||
"inline-comments",
|
||||
@@ -187,9 +184,7 @@ def test_comments():
|
||||
def test_import_csv():
|
||||
shell = TestLimboShell()
|
||||
shell.run_test("memory-db", ".open :memory:", "")
|
||||
shell.run_test(
|
||||
"create-csv-table", "CREATE TABLE csv_table (c1 INT, c2 REAL, c3 String);", ""
|
||||
)
|
||||
shell.run_test("create-csv-table", "CREATE TABLE csv_table (c1 INT, c2 REAL, c3 String);", "")
|
||||
shell.run_test(
|
||||
"import-csv-no-options",
|
||||
".import --csv ./testing/test_files/test.csv csv_table",
|
||||
@@ -206,9 +201,7 @@ def test_import_csv():
|
||||
def test_import_csv_verbose():
|
||||
shell = TestLimboShell()
|
||||
shell.run_test("open-memory", ".open :memory:", "")
|
||||
shell.run_test(
|
||||
"create-csv-table", "CREATE TABLE csv_table (c1 INT, c2 REAL, c3 String);", ""
|
||||
)
|
||||
shell.run_test("create-csv-table", "CREATE TABLE csv_table (c1 INT, c2 REAL, c3 String);", "")
|
||||
shell.run_test(
|
||||
"import-csv-verbose",
|
||||
".import --csv -v ./testing/test_files/test.csv csv_table",
|
||||
@@ -225,9 +218,7 @@ def test_import_csv_verbose():
|
||||
def test_import_csv_skip():
|
||||
shell = TestLimboShell()
|
||||
shell.run_test("open-memory", ".open :memory:", "")
|
||||
shell.run_test(
|
||||
"create-csv-table", "CREATE TABLE csv_table (c1 INT, c2 REAL, c3 String);", ""
|
||||
)
|
||||
shell.run_test("create-csv-table", "CREATE TABLE csv_table (c1 INT, c2 REAL, c3 String);", "")
|
||||
shell.run_test(
|
||||
"import-csv-skip",
|
||||
".import --csv --skip 1 ./testing/test_files/test.csv csv_table",
|
||||
@@ -250,51 +241,33 @@ def test_update_with_limit():
|
||||
limbo.run_test("update-limit", "UPDATE t SET a = 10 LIMIT 1;", "")
|
||||
limbo.run_test("update-limit-result", "SELECT COUNT(*) from t WHERE a = 10;", "1")
|
||||
limbo.run_test("update-limit-zero", "UPDATE t SET a = 100 LIMIT 0;", "")
|
||||
limbo.run_test(
|
||||
"update-limit-zero-result", "SELECT COUNT(*) from t WHERE a = 100;", "0"
|
||||
)
|
||||
limbo.run_test("update-limit-zero-result", "SELECT COUNT(*) from t WHERE a = 100;", "0")
|
||||
limbo.run_test("update-limit-all", "UPDATE t SET a = 100 LIMIT -1;", "")
|
||||
# negative limit is treated as no limit in sqlite due to check for --val = 0
|
||||
limbo.run_test("update-limit-result", "SELECT COUNT(*) from t WHERE a = 100;", "6")
|
||||
limbo.run_test(
|
||||
"udpate-limit-where", "UPDATE t SET a = 333 WHERE b = 5 LIMIT 1;", ""
|
||||
)
|
||||
limbo.run_test(
|
||||
"update-limit-where-result", "SELECT COUNT(*) from t WHERE a = 333;", "1"
|
||||
)
|
||||
limbo.run_test("udpate-limit-where", "UPDATE t SET a = 333 WHERE b = 5 LIMIT 1;", "")
|
||||
limbo.run_test("update-limit-where-result", "SELECT COUNT(*) from t WHERE a = 333;", "1")
|
||||
limbo.quit()
|
||||
|
||||
|
||||
|
||||
def test_update_with_limit_and_offset():
|
||||
limbo = TestLimboShell(
|
||||
"CREATE TABLE t (a,b,c); insert into t values (1,2,3), (4,5,6), (7,8,9), (1,2,3),(4,5,6), (7,8,9);"
|
||||
)
|
||||
limbo.run_test("update-limit-offset", "UPDATE t SET a = 10 LIMIT 1 OFFSET 3;", "")
|
||||
limbo.run_test(
|
||||
"update-limit-offset-result", "SELECT COUNT(*) from t WHERE a = 10;", "1"
|
||||
)
|
||||
limbo.run_test("update-limit-offset-result", "SELECT COUNT(*) from t WHERE a = 10;", "1")
|
||||
limbo.run_test("update-limit-result", "SELECT a from t LIMIT 4;", "1\n4\n7\n10")
|
||||
limbo.run_test(
|
||||
"update-limit-offset-zero", "UPDATE t SET a = 100 LIMIT 0 OFFSET 0;", ""
|
||||
)
|
||||
limbo.run_test(
|
||||
"update-limit-zero-result", "SELECT COUNT(*) from t WHERE a = 100;", "0"
|
||||
)
|
||||
limbo.run_test("update-limit-offset-zero", "UPDATE t SET a = 100 LIMIT 0 OFFSET 0;", "")
|
||||
limbo.run_test("update-limit-zero-result", "SELECT COUNT(*) from t WHERE a = 100;", "0")
|
||||
limbo.run_test("update-limit-all", "UPDATE t SET a = 100 LIMIT -1 OFFSET 1;", "")
|
||||
limbo.run_test("update-limit-result", "SELECT COUNT(*) from t WHERE a = 100;", "5")
|
||||
limbo.run_test(
|
||||
"udpate-limit-where", "UPDATE t SET a = 333 WHERE b = 5 LIMIT 1 OFFSET 2;", ""
|
||||
)
|
||||
limbo.run_test(
|
||||
"update-limit-where-result", "SELECT COUNT(*) from t WHERE a = 333;", "0"
|
||||
)
|
||||
limbo.run_test("udpate-limit-where", "UPDATE t SET a = 333 WHERE b = 5 LIMIT 1 OFFSET 2;", "")
|
||||
limbo.run_test("update-limit-where-result", "SELECT COUNT(*) from t WHERE a = 333;", "0")
|
||||
limbo.quit()
|
||||
|
||||
|
||||
|
||||
def test_insert_default_values():
|
||||
limbo = TestLimboShell(
|
||||
"CREATE TABLE t (a integer default(42),b integer default (43),c integer default(44));"
|
||||
)
|
||||
limbo = TestLimboShell("CREATE TABLE t (a integer default(42),b integer default (43),c integer default(44));")
|
||||
for _ in range(1, 10):
|
||||
limbo.execute_dot("INSERT INTO t DEFAULT VALUES;")
|
||||
limbo.run_test("insert-default-values", "SELECT * FROM t;", "42|43|44\n" * 9)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
|
||||
from cli_tests import console
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from pydantic import BaseModel
|
||||
from cli_tests import console
|
||||
|
||||
|
||||
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
|
||||
|
||||
@@ -81,13 +81,13 @@ class CollateTest(BaseModel):
|
||||
)
|
||||
|
||||
limbo.run_test(
|
||||
"Grouping is performed using the NOCASE collating sequence (Values 'abc', 'ABC', and 'Abc' are placed in the same group).",
|
||||
"Grouping is performed using the NOCASE collating sequence (Values 'abc', 'ABC', and 'Abc' are placed in the same group).", # noqa: E501
|
||||
"SELECT count(*) FROM t1 GROUP BY d ORDER BY 1;",
|
||||
"\n".join(map(lambda x: str(x), [4])),
|
||||
)
|
||||
|
||||
limbo.run_test(
|
||||
"Grouping is performed using the BINARY collating sequence. 'abc' and 'ABC' and 'Abc' form different groups",
|
||||
"Grouping is performed using the BINARY collating sequence. 'abc' and 'ABC' and 'Abc' form different groups", # noqa: E501
|
||||
"SELECT count(*) FROM t1 GROUP BY (d || '') ORDER BY 1;",
|
||||
"\n".join(map(lambda x: str(x), [1, 1, 2])),
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Any, Optional, Union
|
||||
from rich.console import Console, JustifyMethod
|
||||
from rich.theme import Theme
|
||||
from rich.style import Style
|
||||
|
||||
from rich.console import Console, JustifyMethod
|
||||
from rich.style import Style
|
||||
from rich.theme import Theme
|
||||
|
||||
custom_theme = Theme(
|
||||
{
|
||||
@@ -95,6 +95,7 @@ def debug(
|
||||
_stack_offset=_stack_offset + 1,
|
||||
)
|
||||
|
||||
|
||||
def test(
|
||||
*objects: Any,
|
||||
sep: str = " ",
|
||||
@@ -119,4 +120,4 @@ def test(
|
||||
highlight=highlight,
|
||||
log_locals=log_locals,
|
||||
_stack_offset=_stack_offset + 1,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
# Eventually extract these tests to be in the fuzzing integration tests
|
||||
import os
|
||||
import tempfile
|
||||
from faker import Faker
|
||||
from faker.providers.lorem.en_US import Provider as P
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from pydantic import BaseModel
|
||||
from cli_tests import console
|
||||
from enum import Enum
|
||||
import random
|
||||
import sqlite3
|
||||
import tempfile
|
||||
from enum import Enum
|
||||
|
||||
from cli_tests import console
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from faker import Faker
|
||||
from faker.providers.lorem.en_US import Provider as P
|
||||
from pydantic import BaseModel
|
||||
|
||||
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
|
||||
|
||||
@@ -233,11 +234,7 @@ class Table(BaseModel):
|
||||
|
||||
# These statements should always cause a constraint error as there is no where clause here
|
||||
def generate_update(self) -> str:
|
||||
vals = [
|
||||
f"{col.name} = {col.col_type.generate(fake)}"
|
||||
for col in self.columns
|
||||
if col.primary_key
|
||||
]
|
||||
vals = [f"{col.name} = {col.col_type.generate(fake)}" for col in self.columns if col.primary_key]
|
||||
vals = ", ".join(vals)
|
||||
|
||||
return f"UPDATE {self.name} SET {vals};"
|
||||
@@ -374,7 +371,7 @@ def main():
|
||||
tests = all_tests()
|
||||
for test in tests:
|
||||
console.info(test.table)
|
||||
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
||||
with tempfile.NamedTemporaryFile(suffix=".db") as tmp:
|
||||
try:
|
||||
# Use with syntax to automatically close shell on error
|
||||
with TestLimboShell("") as limbo:
|
||||
@@ -387,7 +384,7 @@ def main():
|
||||
|
||||
tests = [custom_test_2, regression_test_update_single_key]
|
||||
for test in tests:
|
||||
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
||||
with tempfile.NamedTemporaryFile(suffix=".db") as tmp:
|
||||
try:
|
||||
with TestLimboShell("") as limbo:
|
||||
limbo.execute_dot(f".open {tmp.name}")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
|
||||
from cli_tests import console
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
|
||||
sqlite_exec = "./scripts/limbo-sqlite3"
|
||||
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
|
||||
@@ -40,14 +41,10 @@ def test_uuid():
|
||||
)
|
||||
limbo.run_test_fn("SELECT uuid4_str();", lambda res: len(res) == 36)
|
||||
limbo.run_test_fn("SELECT hex(uuid7());", lambda res: int(res, 16) is not None)
|
||||
limbo.run_test_fn(
|
||||
"SELECT uuid7_timestamp_ms(uuid7()) / 1000;", lambda res: res.isdigit()
|
||||
)
|
||||
limbo.run_test_fn("SELECT uuid7_timestamp_ms(uuid7()) / 1000;", lambda res: res.isdigit())
|
||||
limbo.run_test_fn("SELECT uuid7_str();", validate_string_uuid)
|
||||
limbo.run_test_fn("SELECT uuid_str(uuid7());", validate_string_uuid)
|
||||
limbo.run_test_fn(
|
||||
"SELECT hex(uuid_blob(uuid7_str()));", lambda res: int(res, 16) is not None
|
||||
)
|
||||
limbo.run_test_fn("SELECT hex(uuid_blob(uuid7_str()));", lambda res: int(res, 16) is not None)
|
||||
limbo.run_test_fn("SELECT uuid_str(uuid_blob(uuid7_str()));", validate_string_uuid)
|
||||
limbo.run_test_fn(
|
||||
f"SELECT uuid7_timestamp_ms('{specific_time}') / 1000;",
|
||||
@@ -160,12 +157,8 @@ def test_aggregates():
|
||||
validate_percentile2,
|
||||
"test aggregate percentile function with 1 argument works",
|
||||
)
|
||||
limbo.run_test_fn(
|
||||
"SELECT percentile_cont(value, 0.25) from test;", validate_percentile1
|
||||
)
|
||||
limbo.run_test_fn(
|
||||
"SELECT percentile_disc(value, 0.55) from test;", validate_percentile_disc
|
||||
)
|
||||
limbo.run_test_fn("SELECT percentile_cont(value, 0.25) from test;", validate_percentile1)
|
||||
limbo.run_test_fn("SELECT percentile_disc(value, 0.55) from test;", validate_percentile_disc)
|
||||
limbo.quit()
|
||||
|
||||
|
||||
@@ -223,8 +216,7 @@ def test_crypto():
|
||||
# Hashing and Decode
|
||||
limbo.run_test_fn(
|
||||
"SELECT crypto_encode(crypto_blake3('abc'), 'hex');",
|
||||
lambda res: res
|
||||
== "6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85",
|
||||
lambda res: res == "6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85",
|
||||
"blake3 should encrypt correctly",
|
||||
)
|
||||
limbo.run_test_fn(
|
||||
@@ -239,8 +231,7 @@ def test_crypto():
|
||||
)
|
||||
limbo.run_test_fn(
|
||||
"SELECT crypto_encode(crypto_sha256('abc'), 'hex');",
|
||||
lambda a: a
|
||||
== "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||
lambda a: a == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||
"sha256 should encrypt correctly",
|
||||
)
|
||||
limbo.run_test_fn(
|
||||
@@ -252,7 +243,7 @@ def test_crypto():
|
||||
limbo.run_test_fn(
|
||||
"SELECT crypto_encode(crypto_sha512('abc'), 'hex');",
|
||||
lambda a: a
|
||||
== "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
|
||||
== "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", # noqa: E501
|
||||
"sha512 should encrypt correctly",
|
||||
)
|
||||
|
||||
@@ -401,9 +392,7 @@ def test_kv():
|
||||
)
|
||||
for i in range(100):
|
||||
limbo.execute_dot(f"insert into t values ('key{i}', 'val{i}');")
|
||||
limbo.run_test_fn(
|
||||
"select count(*) from t;", lambda res: "100" == res, "can insert 100 rows"
|
||||
)
|
||||
limbo.run_test_fn("select count(*) from t;", lambda res: "100" == res, "can insert 100 rows")
|
||||
limbo.run_test_fn("update t set value = 'updated' where key = 'key33';", null)
|
||||
limbo.run_test_fn(
|
||||
"select * from t where key = 'key33';",
|
||||
@@ -422,12 +411,8 @@ def test_kv():
|
||||
"can update all rows",
|
||||
)
|
||||
limbo.run_test_fn("delete from t limit 96;", null, "can delete 96 rows")
|
||||
limbo.run_test_fn(
|
||||
"select count(*) from t;", lambda res: "4" == res, "four rows remain"
|
||||
)
|
||||
limbo.run_test_fn(
|
||||
"update t set key = '100' where 1;", null, "where clause evaluates properly"
|
||||
)
|
||||
limbo.run_test_fn("select count(*) from t;", lambda res: "4" == res, "four rows remain")
|
||||
limbo.run_test_fn("update t set key = '100' where 1;", null, "where clause evaluates properly")
|
||||
limbo.run_test_fn(
|
||||
"select * from t where key = '100';",
|
||||
lambda res: res == "100|updated2",
|
||||
@@ -509,9 +494,7 @@ def test_vfs():
|
||||
ext_path = "target/debug/liblimbo_ext_tests"
|
||||
limbo.run_test_fn(".vfslist", lambda x: "testvfs" not in x, "testvfs not loaded")
|
||||
limbo.execute_dot(f".load {ext_path}")
|
||||
limbo.run_test_fn(
|
||||
".vfslist", lambda res: "testvfs" in res, "testvfs extension loaded"
|
||||
)
|
||||
limbo.run_test_fn(".vfslist", lambda res: "testvfs" in res, "testvfs extension loaded")
|
||||
limbo.execute_dot(".open testing/vfs.db testvfs")
|
||||
limbo.execute_dot("create table test (id integer primary key, value float);")
|
||||
limbo.execute_dot("create table vfs (id integer primary key, value blob);")
|
||||
@@ -742,8 +725,7 @@ def test_tablestats():
|
||||
|
||||
limbo.run_test_fn(
|
||||
"SELECT * FROM stats ORDER BY name;",
|
||||
lambda res: sorted(_split(res))
|
||||
== sorted(["logs|1", "people|3", "products|11", "users|10000"]),
|
||||
lambda res: sorted(_split(res)) == sorted(["logs|1", "people|3", "products|11", "users|10000"]),
|
||||
"stats shows correct initial counts (and skips itself)",
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
|
||||
from cli_tests import console
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
|
||||
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import select
|
||||
from time import sleep
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from time import sleep
|
||||
from typing import Callable, List, Optional
|
||||
from cli_tests import console
|
||||
|
||||
from cli_tests import console
|
||||
|
||||
PIPE_BUF = 4096
|
||||
|
||||
@@ -107,7 +107,7 @@ class TestLimboShell:
|
||||
flags="",
|
||||
):
|
||||
if exec_name is None:
|
||||
exec_name = os.environ.get('SQLITE_EXEC')
|
||||
exec_name = os.environ.get("SQLITE_EXEC")
|
||||
if exec_name is None:
|
||||
exec_name = "./scripts/limbo-sqlite3"
|
||||
if flags == "":
|
||||
@@ -142,10 +142,7 @@ INSERT INTO t VALUES (zeroblob(1024 - 1), zeroblob(1024 - 2), zeroblob(1024 - 3)
|
||||
console.test(f"Running test: {name}", _stack_offset=2)
|
||||
actual = self.shell.execute(sql)
|
||||
assert actual == expected, (
|
||||
f"Test failed: {name}\n"
|
||||
f"SQL: {sql}\n"
|
||||
f"Expected:\n{repr(expected)}\n"
|
||||
f"Actual:\n{repr(actual)}"
|
||||
f"Test failed: {name}\nSQL: {sql}\nExpected:\n{repr(expected)}\nActual:\n{repr(actual)}"
|
||||
)
|
||||
|
||||
def run_debug(self, sql: str):
|
||||
@@ -153,9 +150,7 @@ INSERT INTO t VALUES (zeroblob(1024 - 1), zeroblob(1024 - 2), zeroblob(1024 - 3)
|
||||
actual = self.shell.execute(sql)
|
||||
console.debug(f"OUTPUT:\n{repr(actual)}", _stack_offset=2)
|
||||
|
||||
def run_test_fn(
|
||||
self, sql: str, validate: Callable[[str], bool], desc: str = ""
|
||||
) -> None:
|
||||
def run_test_fn(self, sql: str, validate: Callable[[str], bool], desc: str = "") -> None:
|
||||
# Print the test that is executing before executing the sql command
|
||||
# Printing later confuses the user of the code what test has actually failed
|
||||
if desc:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
|
||||
from cli_tests import console
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from pydantic import BaseModel
|
||||
from cli_tests import console
|
||||
|
||||
|
||||
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
|
||||
|
||||
@@ -39,10 +39,7 @@ class UpdateTest(BaseModel):
|
||||
f"{self.vals}",
|
||||
)
|
||||
|
||||
stmt = [
|
||||
f"SELECT hex(t1), t2, t3 FROM test LIMIT 1 OFFSET {i};"
|
||||
for i in range(self.vals)
|
||||
]
|
||||
stmt = [f"SELECT hex(t1), t2, t3 FROM test LIMIT 1 OFFSET {i};" for i in range(self.vals)]
|
||||
|
||||
expected = [f"{zero_blob}|{t2_val}|{t3_val}" for _ in range(self.vals)]
|
||||
sqlite.run_test(
|
||||
@@ -84,15 +81,10 @@ class UpdateTest(BaseModel):
|
||||
f"{self.vals}",
|
||||
)
|
||||
|
||||
stmt = [
|
||||
f"SELECT hex(t1), t2, t3 FROM test LIMIT 1 OFFSET {i};"
|
||||
for i in range(self.vals)
|
||||
]
|
||||
stmt = [f"SELECT hex(t1), t2, t3 FROM test LIMIT 1 OFFSET {i};" for i in range(self.vals)]
|
||||
|
||||
expected = [
|
||||
f"{zero_blob}|{t2_val}|{t3_val}"
|
||||
if i != 0
|
||||
else f"{zero_blob}|{t2_update_val}|{t3_val}"
|
||||
f"{zero_blob}|{t2_val}|{t3_val}" if i != 0 else f"{zero_blob}|{t2_update_val}|{t3_val}"
|
||||
for i in range(self.vals)
|
||||
]
|
||||
sqlite.run_test(
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# vfs benchmarking/comparison
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import statistics
|
||||
import argparse
|
||||
import os
|
||||
import statistics
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from time import perf_counter, sleep
|
||||
from typing import Dict
|
||||
|
||||
from cli_tests.console import error, info, test
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from cli_tests.console import info, error, test
|
||||
|
||||
LIMBO_BIN = Path("./target/release/limbo")
|
||||
DB_FILE = Path("testing/temp.db")
|
||||
@@ -37,9 +37,7 @@ def bench_one(vfs: str, sql: str, iterations: int) -> list[float]:
|
||||
|
||||
for i in range(1, iterations + 1):
|
||||
start = perf_counter()
|
||||
_ = shell.run_test_fn(
|
||||
sql, lambda x: x is not None and append_time(times, start, perf_counter)
|
||||
)
|
||||
_ = shell.run_test_fn(sql, lambda x: x is not None and append_time(times, start, perf_counter))
|
||||
test(f" {vfs} | run {i:>3}: {times[-1]:.6f}s")
|
||||
|
||||
shell.quit()
|
||||
@@ -60,9 +58,7 @@ def cleanup_temp_db() -> None:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Benchmark a SQL statement against all Limbo VFS back‑ends."
|
||||
)
|
||||
parser = argparse.ArgumentParser(description="Benchmark a SQL statement against all Limbo VFS back‑ends.")
|
||||
parser.add_argument("sql", help="SQL statement to execute (quote it)")
|
||||
parser.add_argument("iterations", type=int, help="number of repetitions")
|
||||
args = parser.parse_args()
|
||||
@@ -105,9 +101,7 @@ def main() -> None:
|
||||
else:
|
||||
pct = (avg - baseline_avg) / baseline_avg * 100.0
|
||||
faster_slower = "slower" if pct > 0 else "faster"
|
||||
info(
|
||||
f"{vfs:<{name_pad}} : {avg:.6f} ({abs(pct):.1f}% {faster_slower} than {baseline})"
|
||||
)
|
||||
info(f"{vfs:<{name_pad}} : {avg:.6f} ({abs(pct):.1f}% {faster_slower} than {baseline})")
|
||||
info("-" * 60)
|
||||
cleanup_temp_db()
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import tempfile
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from pydantic import BaseModel
|
||||
from cli_tests import console
|
||||
from time import sleep
|
||||
|
||||
from cli_tests import console
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from pydantic import BaseModel
|
||||
|
||||
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
|
||||
|
||||
@@ -46,9 +46,7 @@ class InsertTest(BaseModel):
|
||||
big_stmt = "".join(big_stmt)
|
||||
expected = "\n".join(expected)
|
||||
|
||||
limbo.run_test_fn(
|
||||
big_stmt, lambda res: validate_with_expected(res, expected), self.name
|
||||
)
|
||||
limbo.run_test_fn(big_stmt, lambda res: validate_with_expected(res, expected), self.name)
|
||||
|
||||
def test_compat(self):
|
||||
console.info("Testing in SQLite\n")
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sqlite3
|
||||
|
||||
from faker import Faker
|
||||
|
||||
conn = sqlite3.connect('database.db')
|
||||
conn = sqlite3.connect("database.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create the user table
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
first_name TEXT,
|
||||
@@ -20,18 +21,29 @@ cursor.execute('''
|
||||
zipcode TEXT,
|
||||
age INTEGER
|
||||
)
|
||||
''')
|
||||
""")
|
||||
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
price REAL
|
||||
)
|
||||
''')
|
||||
""")
|
||||
|
||||
product_list = ["hat", "cap", "shirt", "sweater", "sweatshirt",
|
||||
"shorts", "jeans", "sneakers", "boots", "coat", "accessories"]
|
||||
product_list = [
|
||||
"hat",
|
||||
"cap",
|
||||
"shirt",
|
||||
"sweater",
|
||||
"sweatshirt",
|
||||
"shorts",
|
||||
"jeans",
|
||||
"sneakers",
|
||||
"boots",
|
||||
"coat",
|
||||
"accessories",
|
||||
]
|
||||
|
||||
fake = Faker()
|
||||
for _ in range(10000):
|
||||
@@ -45,18 +57,23 @@ for _ in range(10000):
|
||||
zipcode = fake.zipcode()
|
||||
age = fake.random_int(min=1, max=100)
|
||||
|
||||
cursor.execute('''
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO users (first_name, last_name, email, phone_number, address, city, state, zipcode, age)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (first_name, last_name, email, phone_number, address, city, state, zipcode, age))
|
||||
""",
|
||||
(first_name, last_name, email, phone_number, address, city, state, zipcode, age),
|
||||
)
|
||||
|
||||
for product in product_list:
|
||||
price = fake.random_int(min=1, max=100)
|
||||
cursor.execute('''
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO products (name, price)
|
||||
VALUES (?, ?)
|
||||
''', (product, price))
|
||||
|
||||
""",
|
||||
(product, price),
|
||||
)
|
||||
|
||||
|
||||
conn.commit()
|
||||
|
||||
246
uv.lock
generated
246
uv.lock
generated
@@ -1,10 +1,13 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[manifest]
|
||||
members = [
|
||||
"antithesis-tests",
|
||||
"limbo",
|
||||
"limbo-test",
|
||||
"pylimbo",
|
||||
"scripts",
|
||||
]
|
||||
|
||||
@@ -17,6 +20,33 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "antithesis"
|
||||
version = "0.1.17"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/cb/162e6392d43263b27577a6d50a2f0338cc22424e36a6beee9a2c09c1220c/antithesis-0.1.17.tar.gz", hash = "sha256:5083676ef84d3b424188056c0c5cf3db584c1e0604338a7e6b34d265caaf801e", size = 15427 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/58/49eef5d79d6b34a90b7a910fceb949c9964857173d1ada85d4c8772c978e/antithesis-0.1.17-py3-none-any.whl", hash = "sha256:0fe7ef78e7a9ea5f9b4b5a1b855c10d79863e7f08b484a29a4b1d599b9a43794", size = 15972 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "antithesis-tests"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "antithesis-tests" }
|
||||
dependencies = [
|
||||
{ name = "antithesis" },
|
||||
{ name = "pylimbo" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "antithesis", specifier = ">=0.1.17" },
|
||||
{ name = "pylimbo", editable = "bindings/python" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.1.31"
|
||||
@@ -70,6 +100,43 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611 },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "44.0.2"
|
||||
@@ -138,6 +205,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "limbo"
|
||||
version = "0.1.0"
|
||||
@@ -176,6 +252,26 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maturin"
|
||||
version = "1.7.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ab/1e/085ddc0e5b08ae7af7a743a0dd6ed06b22a1332288488f1a333137885150/maturin-1.7.8.tar.gz", hash = "sha256:649c6ef3f0fa4c5f596140d761dc5a4d577c485cc32fb5b9b344a8280352880d", size = 195704 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/ed/c8bb26e91c879e418ae1b01630722ed20b6fe0e6755be8d538d83666f136/maturin-1.7.8-py3-none-linux_armv6l.whl", hash = "sha256:c6950fd2790acd93265e1501cea66f9249cff19724654424ca75a3b17ebb315b", size = 7515691 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/7a/573f969315f0b92a09a0a565d45e98812c87796e2e19a7856159ab234faf/maturin-1.7.8-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f98288d5c382bacf0c076871dfd50c38f1eb2248f417551e98dd6f47f6ee8afa", size = 14434454 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/17/46834841fbf19231487f185e68b95ca348cc05cce49be8787e0bc7e9dc47/maturin-1.7.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b2d4e0f674ca29864e6b86c2eb9fee8236d1c7496c25f7300e34229272468f4c", size = 7509122 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/8f/bf8b4871eb390a4baef2e0bb5016852c7c0311a9772e2945534cfa2ee40e/maturin-1.7.8-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:6cafb17bf57822bdc04423d9e3e766d42918d474848fe9833e397267514ba891", size = 7598870 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/43/c842be67a7c59568082345249b956138ae93d0b2474fb41c186ce26d05e1/maturin-1.7.8-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:2b2bdee0c3a84696b3a809054c43ead1a04b7b3321cbd5b8f5676e4ba4691d0f", size = 7932310 },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/12/42435d05f2d6c75eb621751e6f021d29eb34d18e3b9c5c94d828744c2d54/maturin-1.7.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:b8188b71259fc2bc568d9c8acc186fcfed96f42539bcb55b8e6f4ec26e411f37", size = 7321964 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/26/f3272ee985ebf9b3e8c4cd4f4efb022af1e12c9f53aed0dcc9a255399f4e/maturin-1.7.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:a4f58c2a53c2958a1bf090960b08b28e676136cd88ac2f5dfdcf1b14ea54ec06", size = 7408613 },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/7d/be27bcc7d3ac6e6c2136a8ec0cc56f227a292d6cfdde55e095b6c0aa24a9/maturin-1.7.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:c5d6c0c631d1fc646cd3834795e6cfd72ab4271d289df7e0f911261a02bec75f", size = 9496974 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/e8/0d7323e9a31c11edf69c4473d73eca74803ce3e2390abf8ae3ac7eb10b04/maturin-1.7.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c23664d19dadcbf800ef70f26afb2e0485a985c62889930934f019c565534c23", size = 10828401 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/82/5080e052c0d8c9872f6d4b94cae84c17ed7f2ea270d709210ea6445b655f/maturin-1.7.8-py3-none-win32.whl", hash = "sha256:403eebf1afa6f19b49425f089e39c53b8e597bc86a47f3a76e828dc78d27fa80", size = 6845240 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/c9/9b162361ded893f36038c2f8ac6a972ec441c11df8d17c440997eb28090f/maturin-1.7.8-py3-none-win_amd64.whl", hash = "sha256:1ce48d007438b895f8665314b6748ac0dab31e4f32049a60b52281dd2dccbdde", size = 7762332 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/40/46d4742db742f69a7fe0054cd7c82bc79b2d70cb8c91f7e737e75c28a5f3/maturin-1.7.8-py3-none-win_arm64.whl", hash = "sha256:cc92a62953205e8945b6cfe6943d6a8576a4442d30d9c67141f944f4f4640e62", size = 6501353 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
@@ -185,6 +281,46 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/95/a6dbb4fef19402c488b001ff4bd9f1a770e44049ce049b904dcffc65356c/mypy-1.11.0.tar.gz", hash = "sha256:93743608c7348772fdc717af4aeee1997293a1ad04bc0ea6efa15bf65385c538", size = 3078260 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/0e/fc28db5d225623cbbca54d9f4207a3ebf4937d816ba4b61aa3a5ad2af2e7/mypy-1.11.0-py3-none-any.whl", hash = "sha256:56913ec8c7638b0091ef4da6fcc9136896914a9d60d54670a75880c3e5b99ace", size = 2619149 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
@@ -277,6 +413,63 @@ crypto = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pylimbo"
|
||||
source = { editable = "bindings/python" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
dev = [
|
||||
{ name = "coverage" },
|
||||
{ name = "maturin" },
|
||||
{ name = "mypy" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "coverage" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "maturin" },
|
||||
{ name = "mypy" },
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "ruff" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "coverage", marker = "extra == 'dev'", specifier = "==7.6.1" },
|
||||
{ name = "maturin", marker = "extra == 'dev'", specifier = "==1.7.8" },
|
||||
{ name = "mypy", marker = "extra == 'dev'", specifier = "==1.11.0" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = "==8.3.1" },
|
||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = "==5.0.0" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = "==0.5.4" },
|
||||
{ name = "typing-extensions", specifier = ">=4.6.0,!=4.7.0" },
|
||||
]
|
||||
provides-extras = ["dev"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "coverage", specifier = ">=7.6.1" },
|
||||
{ name = "iniconfig", specifier = ">=2.1.0" },
|
||||
{ name = "maturin", specifier = ">=1.7.8" },
|
||||
{ name = "mypy", specifier = ">=1.11.0" },
|
||||
{ name = "mypy-extensions", specifier = ">=1.1.0" },
|
||||
{ name = "pluggy", specifier = ">=1.6.0" },
|
||||
{ name = "pytest", specifier = ">=8.3.1" },
|
||||
{ name = "pytest-cov", specifier = ">=5.0.0" },
|
||||
{ name = "ruff", specifier = ">=0.5.4" },
|
||||
{ name = "typing-extensions", specifier = ">=4.13.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pynacl"
|
||||
version = "1.5.0"
|
||||
@@ -297,6 +490,34 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/7f/a6f79e033aa8318b6dfe2173fa6409ee75dafccf409d90884bf921433d88/pytest-8.3.1.tar.gz", hash = "sha256:7e8e5c5abd6e93cb1cc151f23e57adc31fcf8cfd2a3ff2da63e23f732de35db6", size = 1438997 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/77/ccea391104f576a6e54a54334fc26d29c28aa0ec85714c781fbd2c34ac86/pytest-8.3.1-py3-none-any.whl", hash = "sha256:e9600ccf4f563976e2c99fa02c7624ab938296551f280835ee6516df8bc4ae8c", size = 341628 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "5.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coverage" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
@@ -325,6 +546,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/1a/5955fa22ab088c1f4d8458b4cbc158c6db72143361e8d46e179c48576aab/ruff-0.5.4.tar.gz", hash = "sha256:2795726d5f71c4f4e70653273d1c23a8182f07dd8e48c12de5d867bfb7557eed", size = 2424702 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/34/2235ecce6794345f42ad334d1b14384c70b202f77509e5678b68a640fe78/ruff-0.5.4-py3-none-linux_armv6l.whl", hash = "sha256:82acef724fc639699b4d3177ed5cc14c2a5aacd92edd578a9e846d5b5ec18ddf", size = 9499774 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/23/ffe51028ba274223191d3f96b059108cf7690eb93985a7fdb077c3d1191b/ruff-0.5.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:da62e87637c8838b325e65beee485f71eb36202ce8e3cdbc24b9fcb8b99a37be", size = 8550240 },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/75/843aa3d10a39ab60fbd63f65c825f647907a9732ac27e24d3f94dd2db618/ruff-0.5.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e98ad088edfe2f3b85a925ee96da652028f093d6b9b56b76fc242d8abb8e2059", size = 8160520 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/88/3d0f5244905088cc2fd136fae8ce81f46d145e2449051313c462032d144d/ruff-0.5.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c55efbecc3152d614cfe6c2247a3054cfe358cefbf794f8c79c8575456efe19", size = 9911606 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ff/6546020836408351e7558dedacc6e5ca7f652f76a9d05ac4882c787d45b1/ruff-0.5.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9b85eaa1f653abd0a70603b8b7008d9e00c9fa1bbd0bf40dad3f0c0bdd06793", size = 9286353 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/bf/51e0c5f12a9bf3c7596cf7f45e1b102f8b49f1da39943e03739890bbf6a4/ruff-0.5.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cf497a47751be8c883059c4613ba2f50dd06ec672692de2811f039432875278", size = 10082929 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/0e/a44cb6edb629788de892fc7bb8ac8b47059df94d7ec9c4e52e04bab5be95/ruff-0.5.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:09c14ed6a72af9ccc8d2e313d7acf7037f0faff43cde4b507e66f14e812e37f7", size = 10832586 },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/ca/e3810f701ae472e5fe3180d56fe6fcc92ea94c7490097a0f731f5602f26f/ruff-0.5.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:628f6b8f97b8bad2490240aa84f3e68f390e13fabc9af5c0d3b96b485921cd60", size = 10421967 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/47/a62df6ccd6e5d019271df203ea6564f2022c49f85c0bf6ada708cd7b4a5e/ruff-0.5.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3520a00c0563d7a7a7c324ad7e2cde2355733dafa9592c671fb2e9e3cd8194c1", size = 11371031 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/02/64f24893eea23c447460e6509e9dd0ae18d7a797f67fee1bafed964ebbae/ruff-0.5.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93789f14ca2244fb91ed481456f6d0bb8af1f75a330e133b67d08f06ad85b516", size = 10103164 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/78/683b6c6976fcc33e4a03a0e234e0b9f9b8682f807a661225c829b248de82/ruff-0.5.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:029454e2824eafa25b9df46882f7f7844d36fd8ce51c1b7f6d97e2615a57bbcc", size = 9920056 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/3a/6c67c5d670aae2a51a11713aff819d729ed92cb0b1d962b8df27e4657650/ruff-0.5.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9492320eed573a13a0bc09a2957f17aa733fff9ce5bf00e66e6d4a88ec33813f", size = 9361286 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/f5/da3a0e2fd0bcbdb3d2ff579ef9cb3ca2af71b9bee97fa917c9a9e0500b67/ruff-0.5.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a6e1f62a92c645e2919b65c02e79d1f61e78a58eddaebca6c23659e7c7cb4ac7", size = 9720829 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/56/5062119a5c9e06d98cd6406bfc1eab7616a7c67494a4d05b6052d99dd147/ruff-0.5.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:768fa9208df2bec4b2ce61dbc7c2ddd6b1be9fb48f1f8d3b78b3332c7d71c1ff", size = 10143530 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/76/16f8f1c8d0cba6c96ab6f292146fc0acb6dd633a989f524d3b3ef1ee8364/ruff-0.5.4-py3-none-win32.whl", hash = "sha256:e1e7393e9c56128e870b233c82ceb42164966f25b30f68acbb24ed69ce9c3a4e", size = 7794271 },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/35/d6c3c83fb8817328db73c15b1836ccb0c3ce56b72d0d01d98b3a452bec58/ruff-0.5.4-py3-none-win_amd64.whl", hash = "sha256:58b54459221fd3f661a7329f177f091eb35cf7a603f01d9eb3eb11cc348d38c4", size = 8579021 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/ef/3e732c0152280775f728ab99691c718ee9a4ae79bf5af1dd9258f7fe7fef/ruff-0.5.4-py3-none-win_arm64.whl", hash = "sha256:bd53da65f1085fb5b307c38fd3c0829e76acf7b2a912d8d79cadcdb4875c1eb7", size = 8034239 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scripts"
|
||||
version = "0.1.0"
|
||||
|
||||
Reference in New Issue
Block a user