diff --git a/.gitignore b/.gitignore index 549edad..7e794b1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ .tox build dist -venv +docs diff --git a/.pytest_cache/README.md b/.pytest_cache/README.md new file mode 100644 index 0000000..bb78ba0 --- /dev/null +++ b/.pytest_cache/README.md @@ -0,0 +1,8 @@ +# pytest cache directory # + +This directory contains data from the pytest's cache plugin, +which provides the `--lf` and `--ff` options, as well as the `cache` fixture. + +**Do not** commit this to version control. + +See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information. diff --git a/.pytest_cache/v/cache/nodeids b/.pytest_cache/v/cache/nodeids new file mode 100644 index 0000000..506ef63 --- /dev/null +++ b/.pytest_cache/v/cache/nodeids @@ -0,0 +1,6 @@ +[ + "tests/test_base.py::test_basemodel", + "tests/test_decorators.py::test_inheritance", + "tests/test_decorators.py::test_reference", + "tests/test_decorators.py::test_crossreference" +] \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..c280103 --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +flask-sqlangelo = {editable = true, path = ".", extras = ["test"]} + +[dev-packages] + +[requires] +python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..c180130 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,185 @@ +{ + "_meta": { + "hash": { + "sha256": "e2fb796b1c4f40fa3724b95c0e7e4230ecd3fecfc513308af4672ee6c7bc0aac" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "atomicwrites": { + "hashes": [ + "sha256:6b5282987b21cd79151f51caccead7a09d0a32e89c568bd9e3c4aaa7bbdf3f3a", + "sha256:e16334d50fe0f90919ef7339c24b9b62e6abaa78cd2d226f3d94eb067eb89043" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==1.2.0" + }, + "attrs": { + "hashes": [ + "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", + "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" + ], + "version": "==18.1.0" + }, + "behave": { + "hashes": [ + "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", + "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c" + ], + "markers": "python_version >= '2.6' and python_version != '3.1.*' and python_version != '3.0.*'", + "version": "==1.2.6" + }, + "click": { + "hashes": [ + "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", + "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + ], + "version": "==6.7" + }, + "faker": { + "hashes": [ + "sha256:ea7cfd3aeb1544732d08bd9cfba40c5b78e3a91e17b1a0698ab81bfc5554c628", + "sha256:f6d67f04abfb2b4bea7afc7fa6c18cf4c523a67956e455668be9ae42bccc21ad" + ], + "version": "==0.9.0" + }, + "flask": { + "hashes": [ + "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", + "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" + ], + "version": "==1.0.2" + }, + "flask-sqlalchemy": { + "hashes": [ + "sha256:3bc0fac969dd8c0ace01b32060f0c729565293302f0c4269beed154b46bec50b", + "sha256:5971b9852b5888655f11db634e87725a9031e170f37c0ce7851cf83497f56e53" + ], + "version": "==2.3.2" + }, + "flask-sqlangelo": { + "editable": true, + "extras": [ + "test" + ], + "path": "." + }, + "inflect": { + "hashes": [ + "sha256:12136d8c62ed987847d7773686dd8c079c15ccece4b7a12ad2ccd9b0caf0ec54", + "sha256:7d8e075740de6e16fead120af6ddb9b97f6d708573431113674b73b10a258352" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==1.0.0" + }, + "itsdangerous": { + "hashes": [ + "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" + ], + "version": "==0.24" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "version": "==2.10" + }, + "markupsafe": { + "hashes": [ + "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + ], + "version": "==1.0" + }, + "more-itertools": { + "hashes": [ + "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", + "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", + "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" + ], + "version": "==4.3.0" + }, + "parse": { + "hashes": [ + "sha256:c3cdf6206f22aeebfa00e5b954fcfea13d1b2dc271c75806b6025b94fb490939" + ], + "version": "==1.8.4" + }, + "parse-type": { + "hashes": [ + "sha256:6e906a66f340252e4c324914a60d417d33a4bea01292ea9bbf68b4fc123be8c9", + "sha256:f596bdc75d3dd93036fbfe3d04127da9f6df0c26c36e01e76da85adef4336b3c" + ], + "version": "==0.4.2" + }, + "pluggy": { + "hashes": [ + "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", + "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==0.7.1" + }, + "py": { + "hashes": [ + "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", + "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==1.5.4" + }, + "pytest": { + "hashes": [ + "sha256:2e7c330338b2732ddb992217962e3454aa7290434e75329b1a6739cea41bea6b", + "sha256:4abcd98faeea3eb95bd05aa6a7b121d5f89d72e4d36ddb0dcbbfd1ec9f3651d1" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==3.7.3" + }, + "python-dateutil": { + "hashes": [ + "sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0", + "sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8" + ], + "version": "==2.7.3" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:ef6569ad403520ee13e180e1bfd6ed71a0254192a934ec1dbd3dbf48f4aa9524" + ], + "version": "==1.2.11" + }, + "text-unidecode": { + "hashes": [ + "sha256:5a1375bb2ba7968740508ae38d92e1f889a0832913cb1c447d5e2046061a396d", + "sha256:801e38bd550b943563660a91de8d4b6fa5df60a542be9093f7abf819f86050cc" + ], + "version": "==1.2" + }, + "werkzeug": { + "hashes": [ + "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", + "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" + ], + "version": "==0.14.1" + } + }, + "develop": {} +} diff --git a/README.rst b/README.rst index 88c53ec..7bc7013 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ SQLAngelo ####### - SQLAlchemy with defaults and sugar. + Flask SQLAlchemy with convenience and sugar. Documentation =============== diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..a28ec7c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,7 @@ +[aliases] +test=pytest + +[tool:pytest] +testpaths=tests +addopts=--doctest-modules + diff --git a/sqlangelo/__init__.py b/sqlangelo/__init__.py index a9b9ae5..9bcf772 100644 --- a/sqlangelo/__init__.py +++ b/sqlangelo/__init__.py @@ -1,5 +1,6 @@ """ Wrapper around Flask-SQLAlchemy and friends """ from flask_sqlalchemy import SQLAlchemy +from . import decorators, mixins, types class SQLAngelo(SQLAlchemy): @@ -20,7 +21,17 @@ def __init__(self, app, db_uri, debug=False): # noqa: C901 # connect tot database app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # suppress warning app.config['SQLALCHEMY_DATABASE_URI'] = db_uri - super(SQLAngelo, self).__init__(app) + super(SQLAngelo, self).__init__(app, session_options={ + 'expire_on_commit': False # as per https://gist.github.com/krak3n/9fa1268ee0a92a67f71a + }) # create basemodel self.BaseModel = base.get_base_model(self) + self.decorators = decorators + self.mixins = mixins + self.types = types + + def init(self): + ''' (re)create the database ''' + self.drop_all() + self.create_all() diff --git a/sqlangelo/base.py b/sqlangelo/base.py index 58c2a61..7e33beb 100644 --- a/sqlangelo/base.py +++ b/sqlangelo/base.py @@ -1,4 +1,4 @@ -from .mixins import CRUDMixin, NamingMixin, inflect_engine +from .mixins import CRUD, Naming, inflect_engine def log(msg): @@ -8,7 +8,7 @@ def log(msg): def get_base_model(db): # noqa: C901 - class BaseModel(db.Model, CRUDMixin, NamingMixin): + class BaseModel(db.Model, CRUD, Naming): """ used as super model for all other models :var id: every model should have a unique id @@ -131,6 +131,7 @@ def add_cross_reference(cls, peer_cls, names=None, x_names=None, x_cls=None): setattr(cls, x_names[0], db.relationship( peer_cls.__name__, + lazy='dynamic', secondary=x_cls.__tablename__, backref=db.backref(x_names[1], lazy='dynamic'), **kwargs)) diff --git a/sqlangelo/mixins.py b/sqlangelo/mixins.py index cdd3c87..9aa949a 100644 --- a/sqlangelo/mixins.py +++ b/sqlangelo/mixins.py @@ -5,7 +5,7 @@ inflect_engine = inflect.engine() -class NamingMixin(object): +class Naming(object): """ Provide some convenient names for models. """ @classmethod @@ -23,7 +23,7 @@ def get_plural(cls): return inflect_engine.plural(cls.get_api()) -class IntrospectionMixin(object): +class Introspection(object): """ access to meta data about a model Available as app.db.Introspectionmixin @@ -74,7 +74,7 @@ def to_dict(self, remove=''): return {c: getattr(self, c) for c in self.columns(remove=remove)} -class CRUDMixin(object): +class CRUD(object): """ provide Create, Read, Update and Delete (CRUD) methods Available as app.db.CRUDmixin. @@ -136,8 +136,6 @@ def update(self, commit=True, report=True, **kwargs): self.report('Updating %s "%s": %s' % (self.__class__.__name__, self, pformat(kwargs))) - log('UPDATE %s' % self) - log(' %s' % kwargs) self.before_update(kwargs) for attr, value in kwargs.items(): setattr(self, attr, value) @@ -211,7 +209,7 @@ def bulk_insert(cls, new_records): cls.commit() -class OperationsMixin(object): +class Operations(object): @classmethod def get(cls, id): diff --git a/tests/models.py b/tests/models.py index 5c414bb..9294184 100644 --- a/tests/models.py +++ b/tests/models.py @@ -9,13 +9,7 @@ ######################################## -class BaseModel(db.BaseModel, mixins.CRUDMixin): - __abstract__ = True - -######################################## - - -class Group(BaseModel): +class Group(db.BaseModel): abbr = db.Column(db.Unicode(6), nullable=False) def __str__(self): @@ -23,7 +17,7 @@ def __str__(self): @decorators.add_cross_reference(Group) -@decorators.make_polymorphic_top(BaseModel, 'User Employee') +@decorators.make_polymorphic_top(db.BaseModel, 'User Employee') class User(object): email = db.Column(db.Unicode(30), nullable=False) @@ -31,7 +25,7 @@ def __str__(self): return '%s (%s)' % (self.email, ', '.join([str(g) for g in self.groups])) -class Company(BaseModel): +class Company(db.BaseModel): name = db.Column(db.Unicode(30), nullable=False) def __str__(self): diff --git a/tests/test_base.py b/tests/test_base.py index 056c10c..96a9ce9 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,6 +1,6 @@ -from tests import models +from tests.models import db def test_basemodel(): - assert models.BaseModel.__abstract__ - assert hasattr(models.BaseModel, 'id') + assert db.BaseModel.__abstract__ + assert hasattr(db.BaseModel, 'id') diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..2156592 --- /dev/null +++ b/tox.ini @@ -0,0 +1,11 @@ +# Tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +# envlist = py27, py35 +envlist = py27 + +[testenv] +commands = {envpython} setup.py test