Skip to content
This repository has been archived by the owner on Nov 30, 2023. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rps-aprotasenya committed Sep 22, 2020
0 parents commit d896bb4
Show file tree
Hide file tree
Showing 11 changed files with 358 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build
dist
*.egg-info
.vscode
.idea
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## Remedy Product python library
1 change: 1 addition & 0 deletions rpspy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.0.1"
Empty file added rpspy/db/__init__.py
Empty file.
47 changes: 47 additions & 0 deletions rpspy/db/append_only.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Give methods to work with append only db
# Maintain db columns: id, uid, created_at, is_deleted
from typing import List, Tuple

from sqlalchemy import and_
from sqlalchemy.orm import Query, aliased

from .base_class import Base


def base_append_only_query(model: Base, query: Query) -> Query:
"""
This query is equal to
SELECT tbl1.* FROM :tablename AS tbl1
LEFT JOIN :tablename AS tbl2 ON
tbl1.uid = tbl2.uid AND tbl2.id > tbl1.id
WHERE
tbl2.id IS NULL AND tbl1.is_deleted = false
:return: SQLAlchemy Query
"""
alias = aliased(model)
query = query.outerjoin(alias, and_(alias.uid == model.uid, alias.id > model.id))
query = query.filter(alias.id.is_(None))
query = query.filter(model.is_deleted.is_(False))

return query


def produce_append_only_query(models: List[Base], join_on_chain: List[Tuple[str, str]] = None,
query: Query = None) -> Query:
if not query:
query = Query([*models])

for index, model in enumerate(models):
if index > 0:
# join with previous model in list
left_name, right_name = join_on_chain[index - 1]
prev_model = models[index - 1]

query = query.outerjoin(
model,
getattr(prev_model, left_name) == getattr(model, right_name)
)

query = base_append_only_query(model, query)

return query
29 changes: 29 additions & 0 deletions rpspy/db/base_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from datetime import datetime
from uuid import uuid4

from sqlalchemy import Column, DateTime, Integer, Boolean
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy_utils import UUIDType


def generate_uid():
return uuid4()


class CustomBase(object):
id = Column(Integer, primary_key=True)
uid = Column(
UUIDType,
nullable=False,
default=generate_uid
)
created_at = Column(DateTime, default=datetime.utcnow)
is_deleted = Column(Boolean, default=False, nullable=False)

# Generate __tablename__ automatically
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()


Base = declarative_base(cls=CustomBase)
72 changes: 72 additions & 0 deletions rpspy/db/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Base crud functional
# For more difficult requests use append_only.py manually
# Warning! Fields like id, uid, created_at, is_deleted are required
# id, created_at, is_deleted is not allowed in WHERE statements
from typing import List

from sqlalchemy import and_
from sqlalchemy.orm import Session

from . import append_only
from .base_class import Base

from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound


def _row2dict(row: Base) -> dict:
"""Convert SqlAlchemy object to dict"""
return {column.name: getattr(row, column.name) for column in row.__table__.columns}


def _get_one_pure_dict(rows: List[Base]) -> dict:
"""Return one SqlAlchemy object, that was converted in dict.
From dict removed service field like id, created_at
If object not one, raise exception"""
if len(rows) < 1:
raise NoResultFound
if len(rows) > 1:
raise MultipleResultsFound
obj = rows[0]
obj_dict = _row2dict(obj)
del obj_dict["id"]
del obj_dict["created_at"]
return obj_dict


def select(session: Session, model: Base, filters: dict = None) -> List[Base]:
"""Return some objects"""
if not filters:
filters = {}
query = session.query(model)
query = append_only.produce_append_only_query([model], query=query)
compiled_filters = (getattr(model, param) == value for param, value in filters.items())
query = query.filter(and_(*compiled_filters))
result = query.all()
return result


def insert(session: Session, model: Base, params: dict):
"""Insert one object"""
m = model(**params)
session.add(m)
session.commit()
session.refresh(m)
return m


def update(session: Session, model: Base, filters: dict, params: dict):
"""Update one object"""
rows = select(session, model, filters)
obj_dict = _get_one_pure_dict(rows)
updated = dict(params.items() - obj_dict.items())
if updated:
obj_dict.update(updated)
return insert(session, model, obj_dict)
else:
return rows[0]


def delete(session: Session, model: Base, filters: dict):
"""Delete one object"""
params = {"is_deleted": True}
return update(session, model, filters, params)
111 changes: 111 additions & 0 deletions rpspy/db/dal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from . import crud
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
from typing import TypeVar
import uuid

DataObj = TypeVar('DataObj')
DBDataObj = TypeVar('DBDataObj')


class BaseDal(object):
def __init__(self, session, model, validation_schema):
self._session = session
self._model = model
self.validation_schema = validation_schema

def _select(self, filters=None):
result = crud.select(self._session, self._model, filters)
return result

def _create(self, params):
result = crud.insert(self._session, self._model, params)
return result

def _update(self, filters, params):
result = crud.update(self._session, self._model, filters, params)
return result

def _delete(self, filters):
result = crud.delete(self._session, self._model, filters)
return result

def _get_or_create(self, filters, params):
"""Check model existing, and if not - create it"""
obj = self.one_or_none(filters)
if obj:
return obj
else:
return self.create(params)

def _update_or_create(self, filters, params):
obj = self.one_or_none(filters)
if obj:
return self.update(filters, params)
else:
return self.create(params)

def _one_or_none(self, filters):
"""Return one or None objects"""
result = crud.select(self._session, self._model, filters)
if len(result) == 0:
return None
if len(result) > 1:
raise MultipleResultsFound
return result[0]

def _one(self, filters):
"""If not exactly one result raised exception"""
result = crud.select(self._session, self._model, filters)
if len(result) < 1:
raise NoResultFound
if len(result) > 1:
raise MultipleResultsFound
obj = result[0]
return obj

def get_all(self, filters: dict = None):
result = self._select(filters)
return result

def get(self, uid: uuid.UUID) -> DBDataObj:
filters = {"uid": uid}
obj = self.one(filters)
return self.validation_schema.from_orm(obj)

def create(self, data_obj: DataObj) -> DBDataObj:
params = data_obj.dict(skip_defaults=True)
created_obj = self._create(params)
return self.validation_schema.from_orm(created_obj)

def update(self, uid: uuid.UUID, data_obj: DataObj) -> DBDataObj:
filters = {"uid": uid}
params = {k: v for k, v in data_obj.dict(skip_defaults=True).items() if v is not None}
updated_obj = self._update(filters, params)
return self.validation_schema.from_orm(updated_obj)

def delete(self, uid: uuid.UUID) -> DBDataObj:
filters = {"uid": uid}
deleted_obj = self._delete(filters)
return self.validation_schema.from_orm(deleted_obj)

def get_or_create(self, filters, params) -> DBDataObj:
"""Check model existing, and if not - create it"""
obj = self._get_or_create(filters, params)
return self.validation_schema.from_orm(obj)

def update_or_create(self, filters, params) -> DBDataObj:
obj = self._update_or_create(filters, params)
return self.validation_schema.from_orm(obj)

def one_or_none(self, filters) -> DBDataObj:
"""Return one or None objects"""
obj = self._one_or_none(filters)
if obj:
return self.validation_schema.from_orm(obj)
else:
return None

def one(self, filters) -> DBDataObj:
"""If not exactly one result raised exception"""
obj = self._one(filters)
return self.validation_schema.from_orm(obj)
48 changes: 48 additions & 0 deletions rpspy/db/regular_crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
def _filter_query(query, model, filters):
for key, value in filters.items():
query = query.filter(getattr(model, key) == value)
return query


def create(session, model, params):
obj = model(**params)
session.add(obj)
session.commit()
session.refresh(obj)
return obj


def select(session, model, filters):
query = session.query(model)
query = _filter_query(query, model, filters)
obj = query.all()
return obj


def one(session, model, filters):
query = session.query(model)
query = _filter_query(query, model, filters)
obj = query.one()
return obj


def one_or_none(session, model, filters):
query = session.query(model)
query = _filter_query(query, model, filters)
obj = query.one_or_none()
return obj


def update(session, model, filters, params):
obj = one(session, model, filters)
for key, value in params.items():
setattr(obj, key, value)
session.commit()
return obj


def delete(session, model, filters):
obj = one(session, model, filters)
session.delete(obj)
session.commit()
return obj
10 changes: 10 additions & 0 deletions rpspy/db/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from sqlalchemy import engine_from_config
from sqlalchemy.orm import sessionmaker


def session_maker(config):
engine = engine_from_config(config, prefix="db.", pool_pre_ping=True)

session = sessionmaker(autocommit=False, autoflush=False, bind=engine)

return session
34 changes: 34 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import os
import re
import setuptools

def get_version(package):
'''
Return package version as listed in `__version__` in `init.py`.
'''
with open(os.path.join(package, '__init__.py')) as f:
return re.search('__version__ = [\'"]([^\'"]+)[\'"]', f.read()).group(1)


with open('README.md', 'r') as fh:
long_description = fh.read()

setuptools.setup(
name='rpspy', # Replace with your own username
version=get_version('rpspy'),
author='RPS Team',
author_email='info@remedyproduct.com',
description='Remedy Product python library',
long_description=long_description,
long_description_content_type='text/markdown',
url='https://github.com/remedypointsolutions/rpspy',
packages=setuptools.find_packages(),
classifiers=[
'Programming Language :: Python :: 3',
'Operating System :: OS Independent',
],
install_requires=[
'sqlalchemy>=1.3.5',
],
python_requires='>=3.7',
)

0 comments on commit d896bb4

Please sign in to comment.