Research
Security News
Kill Switch Hidden in npm Packages Typosquatting Chalk and Chokidar
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.
NoORM (Not only ORM) - make your database operations convenient and natural
noorm requires Python 3.10 or newer. Install it from PyPI:
$ pip install true-noorm
Please note that the correct name is "true-noorm".
Impotring noorm
depends on DB you use in your project. Available options:
import noorm.sqlite3 as nm
– for SQLiteimport noorm.aiosqlite as nm
– for asynchronous SQLite via aiosqliteimport noorm.psycopg2 as nm
– for synchronous Postgres via psycopg2import noorm.asyncpg as nm
– for asynchronous Postgres via asyncpgimport noorm.pymysql as nm
– for synchronous MySQL/MariaDB via PyMySQLimport noorm.aiomysql as nm
– for asynchronous MySQL/MariaDB via aiomysqlimport noorm.sqlalchemy_sync as nm
– for synchronous SqlAlchemyimport noorm.sqlalchemy_async as nm
– for asynchronous SqlAlchemyYes, using the SqlAlchemy ORM through NoORM is a nice idea.
After importing "nm" you use @nm.sql_...
decorators to create functions that perform database operations. All other your application code uses these functions as a so-called "DB API layer". The decorators are:
sql_fetch_all
and sql_fetch_scalars
instead, because they give you less possibilites to shoot your leg. These functions are not implemented for asyncpg.Usage of these decorators in different submodules and underlying databases might have own peculiarities, so check docstring documentation of the chosen "nm".
Assume we have a users table with fields:
id
(rowid
in SQLite)username
email
And an orders table:
id
(rowid
in SQLite)user_id
references to user idorder_date
(TEXT in SQLite)amount
(TEXT in SQLite)from dataclasses import dataclass
from decimal import Decimal
from datetime import datetime
import sqlite3
import noorm.sqlite3 as nm
@dataclass
class DbUser: # When we need only basic info. Not a "model"! Just a dataclass!
id: int
username: str
email: str
@nm.sql_fetch_all(DbUser, "SELECT rowid AS id, username, email FROM users")
def get_all_users():
pass # no parameters, so just "pass"
@nm.sql_one_or_none(
DbUser, "SELECT rowid AS id, username, email FROM users WHERE rowid = :id"
)
def get_user_by_id(id_: int):
return nm.params(id=id_)
@dataclass
class DbUserWithOrdersSummary: # With additional info from `orders` table
id: int
username: str
sum_orders: Decimal | None # SQLite noorm can make decimal out of TEXT
first_order: datetime | None # and datatime too.
last_order: datetime | None
@nm.sql_fetch_all(
DbUserWithOrdersSummary,
"""SELECT
u.rowid AS id, u.username,
SUM(o.amount) AS sum_orders,
MIN(o.order_date) AS first_order, MAX(o.order_date) AS last_order
FROM users u
LEFT OUTER JOIN orders o ON o.user_id = u.rowid
GROUP BY u.rowid, u.username ORDER BY u.rowid
""",
)
def get_users_with_order_summary():
pass
def main():
with sqlite3.connect("data.sqlite") as conn:
# will use our DB API functions
for usr in get_all_users(conn):
print(usr)
print(f"{get_user_by_id(conn, 1)=}")
for usr_summary in get_users_with_order_summary(conn):
print(usr_summary)
Will use asynchronous version of SqlAlchemy "nm".
import sqlalchemy as sa
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
import noorm.sqlalchemy_async as nm
...
@nm.sql_fetch_all(DbUser)
def get_all_users(): # Notice no "async"
return sa.select(User.id, User.username, User.email)
@nm.sql_one_or_none(DbUser)
def get_user_by_id(id_: int):
return sa.select(User.id, User.username, User.email).where(User.id == id_)
@nm.sql_fetch_all(DbUserWithOrdersSummary)
def get_users_with_order_summary():
return (
sa.select(
User.id,
User.username,
sa.func.sum(Order.amount).label("sum_orders"),
sa.func.min(Order.order_date).label("first_order"),
sa.func.max(Order.order_date).label("last_order"),
)
.select_from(User) # `User` and `Order` are ORM model classes
.outerjoin(Order, Order.user_id == User.id)
.group_by(User.id)
.order_by(User.id)
)
async def main():
engine = create_async_engine("sqlite+aiosqlite:///data.sqlite")
async with AsyncSession(engine) as session:
for usr in await get_all_users(session): # Notice "await"
print(usr)
print(f"user_1={await get_user_by_id(session, 1)}")
for usr_summary in await get_users_with_order_summary(session):
print(usr_summary)
db_api/users.py
and order processing functions in db_api/orders.py
.db/db_api/
folder, and specific functions in subsystem folders like external_api/db_api/
.@nm.sql_iterate
and @nm.sql_iterate_scalars
decorators.If, for any reason, you need to terminate execution and produce a default result in your function, raise the nm.CancelExecException
exception. Example:
@nm.sql_fetch_all(DbOrder)
def get_orders_by_ids(order_ids: list[int]):
if not order_ids:
raise nm.CancelExecException
return select(Order.id, Order.date, Order.amount).where(
Order.id.in_(order_ids) # <<< empty list is not acceptable here
)
The nm.CancelExecException
triggers production of a default empty result without querying the DB:
@nm.sql_fetch_all
and @nm.sql_fetch_scalars
return an empty list@nm.sql_one_or_none
and @nm.sql_scalar_or_none
return None@nm.sql_execute
takes no actionObservability is crucial. This library facilitates collecting statistics on DB API function usage out of the box. Statistics include:
from noorm.registry import get_registry
registry = get_registry()
stat = registry.stat_by_name["db.db_api.orders.get_orders_by_user"]
print(stat) # Stat(calls=3, duration=0.0324, tuples=11, fails=0, fails_by_error={})
To collect statistics in a multiprocessing application, initialize this option in your MainProcess:
# Example for uvicorn
import uvicorn
from noorm.registry import get_registry
async def app(scope, receive, send):
...
if __name__ == "__main__":
get_registry().init_multiprocess_registry() # <<< here
uvicorn.run("main:app", port=5000, workers=3, log_level="info")
Consequently, all DB operations occurring in child processes will be aggregated in the MainProcess registry.
Important: statistics for @nm.sql_iterate
and @nm.sql_iterate_scalars
is not precise:
stat.duration
is counted only for query execution and first row extraction.stat.tuples
is always zero.stat.fails
and stat.fails_by_error
do not counter errors that might happen after successful first row extraction.To call an unwrapped version of a DB API function for evaluation, testing, or debugging purposes:
orders_list = await get_orders_by_user(session, 1) # A "normal" call
orders_list_q = get_orders_by_user.unwrapped(1) # An "unwrapped" call
print(str(orders_list_q.compile())) # Want to see a compiled SqlAlchemy "SELECT ..."
Inspired and sponsored by FRAMEN
FAQs
NoORM (Not only ORM) - make your database operations convenient and natural
We found that true-noorm demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.
Security News
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
Product
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.