PK!ۥ#lsqla_wrapper/__init__.pyfrom .core import SQLAlchemy # noqa from .base_query import BaseQuery # noqa from .model import Model, DefaultMeta # noqa from .paginator import Paginator, sanitize_page_number # noqa __version__ = '2.0.1' PK!:4sqla_wrapper/base_query.pyfrom sqlalchemy.orm import Query from .paginator import Paginator class BaseQuery(Query): """The default query object used for models. This can be subclassed and replaced for individual models by setting the :attr:`~SQLAlchemy.query_cls` attribute. This is a subclass of a standard SQLAlchemy :class:`~sqlalchemy.orm.query.Query` class and has all the methods of a standard query as well. """ def get_or_error(self, uid, error): """Like :meth:`get` but raises an error if not found instead of returning `None`. """ rv = self.get(uid) if rv is None: if isinstance(error, Exception): raise error return error() return rv def first_or_error(self, error): """Like :meth:`first` but raises an error if not found instead of returning `None`. """ rv = self.first() if rv is None: if isinstance(error, Exception): raise error return error() return rv def paginate(self, **kwargs): """Paginate this results. Returns an :class:`Paginator` object. """ return Paginator(self, **kwargs) PK!msqla_wrapper/core.pyimport sqlalchemy from sqlalchemy.engine.url import make_url from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker, Session from sqlalchemy.util import get_cls_kwargs from .base_query import BaseQuery from .model import Model, DefaultMeta from .session_proxy import SessionProxyMixin class SQLAlchemy(SessionProxyMixin): """This class is used to easily instantiate a SQLAlchemy connection to a database, to provide a base class for your models, and to get a session to interact with them. .. sourcecode:: python db = SQLAlchemy(_uri_to_database_) class User(db.Model): login = Column(String(80), unique=True) passw_hash = Column(String(80)) .. warning:: **IMPORTANT** In a web application or a multithreaded environment you need to call ``session.remove()`` when a request/thread ends. Use your framework's ``after_request`` hook, to do that. For example, in `Flask`: .. sourcecode:: python app = Flask(…) db = SQLAlchemy(…) @app.teardown_appcontext def shutdown(response=None): db.remove() return response Use the ``db`` to interact with the data: .. sourcecode:: python user = User('tiger') db.add(user) db.commit() # etc To query, you can use ``db.query`` .. sourcecode:: python db.query(User).all() db.query(User).filter_by(login == 'tiger').first() # etc. .. tip:: **Scoping** By default, sessions are scoped to the current thread, but he SQLAlchemy documentation recommends scoping the session to something more application-specific if you can, like a web request in a web app. To do that, you can use the ``scopefunc`` argument, passing a function that returns something unique (and hashable) like a request. """ def __init__(self, url='sqlite://', *, metadata=None, metaclass=None, model_class=Model, scopefunc=None, **options): self.url = url self.info = make_url(url) self.scopefunc = scopefunc self.Model = self._make_declarative_base(model_class, metadata, metaclass) self._update_options(options) self.engine = sqlalchemy.create_engine(url, **self.engine_options) self.Session = sessionmaker(bind=self.engine, **self.session_options) self._session = scoped_session(self.Session, scopefunc) _include_sqlalchemy(self) def _update_options(self, options): session_options = {} for arg in get_cls_kwargs(Session): if arg in options: session_options[arg] = options.pop(arg) options.setdefault('echo', False) options.setdefault('convert_unicode', True) self.engine_options = options session_options.setdefault('autoflush', True) session_options.setdefault('autocommit', False) session_options.setdefault('query_cls', BaseQuery) self.session_options = session_options def _make_declarative_base(self, model_class, metadata=None, metaclass=None): """Creates the declarative base.""" return declarative_base( cls=model_class, name='Model', metadata=metadata, metaclass=metaclass if metaclass else DefaultMeta ) @property def metadata(self): """Proxy for ``Model.metadata``.""" return self.Model.metadata def create_all(self, *args, **kwargs): """Creates all tables.""" kwargs.setdefault('bind', self.engine) self.Model.metadata.create_all(*args, **kwargs) def drop_all(self, *args, **kwargs): """Drops all tables.""" kwargs.setdefault('bind', self.engine) self.Model.metadata.drop_all(*args, **kwargs) def reconfigure(self, **kwargs): """Updates the session options.""" self._session.remove() self.session_options.update(**kwargs) self._session.configure(**self.session_options) def __repr__(self): return "".format(self.url) def _include_sqlalchemy(obj): for module in sqlalchemy, sqlalchemy.orm: for key in module.__all__: if not hasattr(obj, key): setattr(obj, key, getattr(module, key)) # Note: obj.Table does not attempt to be a SQLAlchemy Table class. obj.Table = _make_table(obj) obj.event = sqlalchemy.event def _make_table(db): def _make_table(*args, **kwargs): if len(args) > 1 and isinstance(args[1], db.Column): args = (args[0], db.metadata) + args[1:] return sqlalchemy.Table(*args, **kwargs) return _make_table PK!*sqla_wrapper/model.pyimport inflection import sqlalchemy as sa from sqlalchemy.ext.declarative import DeclarativeMeta, declared_attr from sqlalchemy.schema import _get_table_key class Model(object): """Baseclass for custom user models. """ def __iter__(self): """Returns an iterable that supports .next() so we can do dict(sa_instance). """ for k in self.__dict__.keys(): if not k.startswith('_'): yield (k, getattr(self, k)) def __repr__(self): return '<{}>'.format(self.__class__.__name__) class NameMetaMixin(object): def __init__(cls, name, bases, dic): if should_set_tablename(cls): cls.__tablename__ = _get_table_name(cls.__name__) super().__init__(name, bases, dic) # __table_cls__ has run at this point # if no table was created, use the parent table if ( '__tablename__' not in cls.__dict__ and '__table__' in cls.__dict__ and cls.__dict__['__table__'] is None ): del cls.__table__ def __table_cls__(cls, *args, **kwargs): """This is called by SQLAlchemy during mapper setup. It determines the final table object that the model will use. If no primary key is found, that indicates single-table inheritance, so no table will be created and ``__tablename__`` will be unset. """ # check if a table with this name already exists # allows reflected tables to be applied to model by name key = _get_table_key(args[0], kwargs.get('schema')) if key in cls.metadata.tables: return sa.Table(*args, **kwargs) # if a primary key or constraint is found, create a table for # joined-table inheritance for arg in args: if ( (isinstance(arg, sa.Column) and arg.primary_key) or isinstance(arg, sa.PrimaryKeyConstraint) ): return sa.Table(*args, **kwargs) # if no base classes define a table, return one # ensures the correct error shows up when missing a primary key for base in cls.__mro__[1:-1]: if '__table__' in base.__dict__: break else: return sa.Table(*args, **kwargs) # single-table inheritance, use the parent tablename if '__tablename__' in cls.__dict__: del cls.__tablename__ class DefaultMeta(NameMetaMixin, DeclarativeMeta): pass def _get_table_name(classname): """Generates a table name based on a pluralized and underscored class name. >>> _get_table_name('Document') 'documents' >>> _get_table_name('ToDo') 'to_dos' >>> _get_table_name('UserTestCase') 'user_test_cases' >>> _get_table_name('URL') 'urls' >>> _get_table_name('HTTPRequest') 'http_requests' """ return inflection.pluralize(inflection.underscore(classname)) def should_set_tablename(cls): """Determine whether ``__tablename__`` should be automatically generated for a model. * If no class in the MRO sets a name, one should be generated. * If a declared attr is found, it should be used instead. * If a name is found, it should be used if the class is a mixin, otherwise one should be generated. * Abstract models should not have one generated. Later, :meth:`.DefaultMeta.__table_cls__` will determine if the model looks like single or joined-table inheritance. If no primary key is found, the name will be unset. """ if ( cls.__dict__.get('__abstract__', False) or not any(isinstance(b, DeclarativeMeta) for b in cls.__mro__[1:]) ): return False for base in cls.__mro__: if '__tablename__' not in base.__dict__: continue if isinstance(base.__dict__['__tablename__'], declared_attr): return False return not ( base is cls or base.__dict__.get('__abstract__', False) or not isinstance(base, DeclarativeMeta) ) return True PK!xxsqla_wrapper/paginator.py""" Paginator A helper class for simple pagination of any iterable, like a SQLAlchemy query result or even a list. """ from math import ceil DEFAULT_PER_PAGE = 10 def sanitize_page_number(page): """A helper function for cleanup a ``page`` argument. Cast a string to integer and check that the final value is positive. If the value is not valid returns 1. """ if isinstance(page, str) and page.isdigit(): page = int(page) if isinstance(page, int) and (page > 0): return page return 1 class Paginator(object): """Helper class for paginate data. You can construct it from any SQLAlchemy query object or other iterable. """ showing = 0 total = 0 def __init__(self, query, page=1, per_page=DEFAULT_PER_PAGE, total=None, padding=0, on_error=None): """ :query: Iterable to paginate. Can be a query results object, a list or any other iterable. :page: Current page. :per_page: Max number of items to display on each page. :total: Total number of items. If provided, no attempt wll be made to calculate it from the ``query`` argument. :padding: Number of elements of the next page to show. :on_error: Used if the page number is too big for the total number of items. Raised if it's an exception, called otherwise. ``None`` by default. """ self.query = query # The number of items to be displayed on a page. assert isinstance(per_page, int) and (per_page > 0), \ '`per_page` must be a positive integer' self.per_page = per_page # The total number of items matching the query. if total is None: try: # For counting no need to waste time with ordering total = query.order_by(None).count() except (TypeError, AttributeError): total = query.__len__() self.total = total # The current page number (1 indexed) page = sanitize_page_number(page) if page == 'last': page = self.num_pages self.page = page # The number of items in the current page (could be less than per_page) if total > per_page * page: showing = per_page else: showing = total - per_page * (page - 1) self.showing = showing if showing == 0 and on_error: if isinstance(on_error, Exception): raise on_error return on_error() self.padding = padding def __bool__(self): return self.total > 0 __nonzero__ = __bool__ @property def num_pages(self): """The total number of pages.""" return int(ceil(self.total / float(self.per_page))) @property def is_paginated(self): """True if a more than one page exists.""" return self.num_pages > 1 @property def has_prev(self): """True if a previous page exists.""" return self.page > 1 @property def has_next(self): """True if a next page exists.""" return self.page < self.num_pages @property def next_num(self): """Number of the next page.""" return self.page + 1 @property def prev_num(self): """Number of the previous page.""" return self.page - 1 @property def prev(self): """Returns a :class:`Paginator` object for the previous page.""" if self.has_prev: return Paginator(self.query, self.page - 1, per_page=self.per_page) @property def next(self): """Returns a :class:`Paginator` object for the next page.""" if self.has_next: return Paginator(self.query, self.page + 1, per_page=self.per_page) @property def start_index(self): """0-based index of the first element in the current page.""" return (self.page - 1) * self.per_page @property def end_index(self): """0-based index of the last element in the current page.""" end = self.start_index + self.per_page - 1 return min(end, self.total - 1) def get_range(self, sep=u' - '): return sep.join([str(self.start_index + 1), str(self.end_index + 1)]) @property def items(self): offset = (self.page - 1) * self.per_page offset = max(offset - self.padding, 0) limit = self.per_page + self.padding if self.page > 1: limit = limit + self.padding if hasattr(self.query, 'limit') and hasattr(self.query, 'offset'): return self.query.limit(limit).offset(offset) return self.query[offset:offset + limit] def __iter__(self): for i in self.items: yield i @property def pages(self): """Iterates over the page numbers in the pagination.""" return self.iter_pages() def iter_pages(self, left_edge=2, left_current=3, right_current=4, right_edge=2): """Iterates over the page numbers in the pagination. The four parameters control the thresholds how many numbers should be produced from the sides:: [ 1..left_edge None (current - left_current), current, (current + right_current) None (num_pages - right_edge)..num_pages ] Example: .. sourcecode:: python >>> pg = Paginator(range(1, 199), page=10) >>> list(pg.iter_pages( ... left_edge=2, left_current=2, right_current=5, right_edge=2 ... )) [1, 2, None, 8, 9, 10, 11, 12, 13, 14, 15, None, 19, 20] Skipped page numbers are represented as ``None``. This is one way how you could render such a pagination in the template: .. sourcecode:: html+jinja {% macro render_paginator(paginator, endpoint) %}

Showing {{ paginator.showing }} or {{ paginator.total }}

    {%- if paginator.has_prev %}
  1. «
  2. {% else %}
  3. «
  4. {%- endif %} {%- for page in paginator.pages %} {% if page %} {% if page != paginator.page %}
  5. {{ page }}
  6. {% else %}
  7. {{ page }}
  8. {% endif %} {% else %}
  9. {% endif %} {%- endfor %} {%- if paginator.has_next %}
  10. »
  11. {% else %}
  12. »
  13. {%- endif %}
{% endmacro %} """ last = 0 for num in range(1, self.num_pages + 1): is_active_page = ( num <= left_edge or ( (num >= self.page - left_current) and (num <= self.page + right_current) ) or (num > self.num_pages - right_edge) ) if is_active_page: if last + 1 != num: yield None yield num last = num PK!qvvsqla_wrapper/session_proxy.py class SessionProxyMixin(object): @property def query(self): """Proxy for ``self._session.query``.""" return self._session.query # pragma:no cover def add(self, *args, **kwargs): """Proxy for ``self._session.add()``.""" return self._session.add(*args, **kwargs) # pragma:no cover def add_all(self, *args, **kwargs): """Proxy for ``self._session.add_all()``.""" return self._session.add_all(*args, **kwargs) # pragma:no cover def begin(self, *args, **kwargs): """Proxy for ``self._session.begin()``.""" return self._session.begin(*args, **kwargs) # pragma:no cover def begin_nested(self, *args, **kwargs): """Proxy for ``self._session.begin_nested()``.""" return self._session.begin_nested(*args, **kwargs) # pragma:no cover def commit(self, *args, **kwargs): """Proxy for ``self._session.commit()``.""" return self._session.commit(*args, **kwargs) # pragma:no cover def connection(self, *args, **kwargs): """Proxy for ``self._session.connection()``.""" return self._session.connection(*args, **kwargs) # pragma:no cover def delete(self, *args, **kwargs): """Proxy for ``self._session.delete()``.""" return self._session.delete(*args, **kwargs) # pragma:no cover def execute(self, *args, **kwargs): """Proxy for ``self._session.execute()``.""" return self._session.execute(*args, **kwargs) # pragma:no cover def expire(self, *args, **kwargs): """Proxy for ``self._session.expire()``.""" return self._session.expire(*args, **kwargs) # pragma:no cover def expire_all(self, *args, **kwargs): """Proxy for ``self._session.expire_all()``.""" return self._session.expire_all(*args, **kwargs) # pragma:no cover def expunge(self, *args, **kwargs): """Proxy for ``self._session.expunge()``.""" return self._session.expunge(*args, **kwargs) # pragma:no cover def expunge_all(self, *args, **kwargs): """Proxy for ``self._session.expunge_all()``.""" return self._session.expunge_all(*args, **kwargs) # pragma:no cover def flush(self, *args, **kwargs): """Proxy for ``self._session.flush()``.""" return self._session.flush(*args, **kwargs) # pragma:no cover def invalidate(self, *args, **kwargs): """Proxy for ``self._session.invalidate()``.""" return self._session.invalidate(*args, **kwargs) # pragma:no cover def is_modified(self, *args, **kwargs): """Proxy for ``self._session.is_modified()``.""" return self._session.is_modified(*args, **kwargs) # pragma:no cover def merge(self, *args, **kwargs): """Proxy for ``self._session.merge()``.""" return self._session.merge(*args, **kwargs) # pragma:no cover def prepare(self, *args, **kwargs): """Proxy for ``self._session.prepare()``.""" return self._session.prepare(*args, **kwargs) # pragma:no cover def prune(self, *args, **kwargs): """Proxy for ``self._session.prune()``.""" return self._session.prune(*args, **kwargs) # pragma:no cover def refresh(self, *args, **kwargs): """Proxy for ``self._session.refresh()``.""" return self._session.refresh(*args, **kwargs) # pragma:no cover def rollback(self, *args, **kwargs): """Proxy for ``self._session.rollback()``.""" return self._session.rollback(*args, **kwargs) # pragma:no cover def scalar(self, *args, **kwargs): """Proxy for ``self._session.scalar()``.""" return self._session.scalar(*args, **kwargs) # pragma:no cover PK!Nj>F F $sqla_wrapper-2.0.1.dist-info/LICENSECopyright (c) Juan-Pablo Scaletti Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of the author shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the author. --------- Some portions of the code were extracted and adapted from code of the Flask-SQLAlchemy project. Flask-SQLAlchemy Copyright (c) 2010-2017 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PK!HڽTU"sqla_wrapper-2.0.1.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H8W\%sqla_wrapper-2.0.1.dist-info/METADATAWnF}W ˁDǹuu4n蓹"$]V{f.Ö%\̜eJwN&'q9#R;ږm2: yWn2: ;cog)e4z=1^VŊ T[v=YVU!V':iy֪%XyߺC U:-L}4(7oϯyHJ>C _OjfjQvJW}D+NN[v ƫӗY752m-H̽,4Kkʮ4kmM/ƏD/PtڕEZĻ95kfxO(%7suu[=qDԢpeJI=ZۯnWtsSq@]qx}h~{8M J,L 8촗گE3fWX݊ٙBզE{G3|< 蓷jݧHt+HM`|>+tjӶY&D ?<.$A4Lg .L)a'q mSj_n# Q\J3"=jQ0җte^IHiu%  %)V m^v|UJt#-(0\L }x7 ?cJ196$WLy6ر6aŸD;Lc G(~a'\ YaئdT`;2,as&!&qHsZ; mP~Fl8&';5 J܁ons`@A[)݌/9dIr<9-AU6ITv>XN*tM2G)cTh<|Y#Zc8 NrtC9ϯGy~nƜ h/1YGA>4Vt{؍bkhtZZƳm@6ip:kB;_X] è'>l/4BC+D\OGjCS1h0ir bf"TS">[՚3<ݶA-B~ uV""[w E)#^" LKTv#g0:RWF6elw^_=|4dZ 3[EkK ?=jΪJG$}@ Ne#D8E6_#B w; ; v1J$݃HgְV#8Y@ݭV QTąݒpaA4JVaKTwd؇=P׆'_F،^F%js*FH; 1kY+^UzZL`c}Vv3ζ@U죖Mkw ZIeAId-6As$juW.7v7jp.Eg;8h(_pt-hZ,@+,lO?兄Ƿ_>IV͘/7a mQ~¾?PK!Hyhz #sqla_wrapper-2.0.1.dist-info/RECORD}K0txpFy(\RQ@TUu7HG-bNۇLVnl8Hw臷Wc >m6ԗk+Y?gE(?{yv֩n_s4KPΥ3ۡ>Og.H"eKH70bWe_Eڨ+[%l&̀c5=!*g^J4ߢtwԶ:4*Pa&һfd=e//^M (:ʮv[\?P5WXqi[FdiΜ=PѰSR,v=[B|e"Oũ4=aOpsװ$%u >] n ƓهtdDz6?j7;<5x u#$3$ ڽW[+M=l/ùi*w"w3DPHR㴒&w}oPK!ۥ#lsqla_wrapper/__init__.pyPK!:4 sqla_wrapper/base_query.pyPK!msqla_wrapper/core.pyPK!*sqla_wrapper/model.pyPK!xx])sqla_wrapper/paginator.pyPK!qvv Hsqla_wrapper/session_proxy.pyPK!Nj>F F $Vsqla_wrapper-2.0.1.dist-info/LICENSEPK!HڽTU"Ebsqla_wrapper-2.0.1.dist-info/WHEELPK!H8W\%bsqla_wrapper-2.0.1.dist-info/METADATAPK!Hyhz #xisqla_wrapper-2.0.1.dist-info/RECORDPK k