This repository has been archived by the owner on Nov 30, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d896bb4
Showing
11 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
build | ||
dist | ||
*.egg-info | ||
.vscode | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
## Remedy Product python library |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__version__ = "0.0.1" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
) |