From d58ea13482cf228d61eadf9fdc7848228773f8e7 Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Mon, 18 Feb 2013 18:57:14 +0200 Subject: [PATCH] initial draft --- .travis.yml | 9 +++++ CHANGES.rst | 10 +++++ LICENSE | 27 ++++++++++++++ MANIFEST.in | 7 ++++ README.md | 4 -- README.rst | 12 ++++++ requirements-dev.txt | 5 +++ requirements.txt | 1 + setup.py | 53 +++++++++++++++++++++++++++ sqlalchemy_utils/__init__.py | 71 ++++++++++++++++++++++++++++++++++++ 10 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 .travis.yml create mode 100644 CHANGES.rst create mode 100644 LICENSE create mode 100644 MANIFEST.in delete mode 100644 README.md create mode 100644 README.rst create mode 100644 requirements-dev.txt create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 sqlalchemy_utils/__init__.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7685239 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: python +python: + - 2.5 + - 2.6 + - 2.7 +install: + - pip install -q -e . --use-mirrors +script: + - python setup.py test diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..d18da69 --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,10 @@ +Changelog +--------- + +Various utility functions that I use alongside SQLAlchemy. + + +0.1.0 (12.1.2013) +^^^^^^^^^^^^^^^^ + +- Initial public release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d604ce8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012, Konsta Vesterinen + +All 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 HOLDER 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. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..cd07949 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include CHANGES.rst LICENSE README.rst +recursive-include tests * +recursive-exclude tests *.pyc +recursive-include docs * +recursive-exclude docs *.pyc +prune docs/_build +exclude docs/_themes/.git diff --git a/README.md b/README.md deleted file mode 100644 index 32421c1..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -sqlalchemy-utils -================ - -Various utility functions that I use alongside SQLAlchemy. \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..c2553f5 --- /dev/null +++ b/README.rst @@ -0,0 +1,12 @@ +SQLAlchemy-Utils +================ + +Various utility functions that I use alongside SQLAlchemy. + + +Resources +--------- + +- `Documentation `_ +- `Issue Tracker `_ +- `Code `_ diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..e2929c0 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,5 @@ +-r requirements.txt +pytest==2.2.3 +Pygments==1.2 +Jinja2==2.3 +docutils>=0.10 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0d54465 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +SQLAlchemy==0.7.8 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4078f23 --- /dev/null +++ b/setup.py @@ -0,0 +1,53 @@ +""" +SQLAlchemy-Searchable +--------------------- + +Various utility functions I use with SQLAlchemy. +""" + +from setuptools import setup, Command +import subprocess + + +class PyTest(Command): + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + errno = subprocess.call(['py.test']) + raise SystemExit(errno) + +setup( + name='SQLAlchemy-Utils', + version='0.1', + url='https://github.com/kvesteri/sqlalchemy-utils', + license='BSD', + author='Konsta Vesterinen', + author_email='konsta@fastmonkeys.com', + description=( + 'Various utility functions I use with SQLAlchemy.' + ), + long_description=__doc__, + packages=['sqlalchemy_utils'], + zip_safe=False, + include_package_data=True, + platforms='any', + install_requires=[ + 'SQLAlchemy==0.7.8', + ], + cmdclass={'test': PyTest}, + classifiers=[ + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + 'Topic :: Software Development :: Libraries :: Python Modules' + ] +) diff --git a/sqlalchemy_utils/__init__.py b/sqlalchemy_utils/__init__.py new file mode 100644 index 0000000..30c276a --- /dev/null +++ b/sqlalchemy_utils/__init__.py @@ -0,0 +1,71 @@ +from flask import request +from sqlalchemy.orm.mapper import Mapper +from sqlalchemy.orm.query import _ColumnEntity +from sqlalchemy.sql.expression import desc, asc + + +def sort_query(query, sort): + """ + Applies an sql ORDER BY for given query + + :param query: query to be modified + :param sort: string that defines the label or column to sort the query by + """ + entities = [entity.entity_zero.class_ for entity in query._entities] + for mapper in query._join_entities: + if isinstance(mapper, Mapper): + entities.append(mapper.class_) + else: + entities.append(mapper) + + # get all label names for queries such as: + # db.session.query(Category, db.func.count(Article.id).label('articles')) + labels = [] + for entity in query._entities: + if isinstance(entity, _ColumnEntity) and entity._label_name: + labels.append(entity._label_name) + + sort = request.args.get('sort', sort) + if not sort: + return query + + if sort[0] == '-': + func = desc + sort = sort[1:] + else: + func = asc + + component = None + parts = sort.split('-') + if len(parts) > 1: + component = parts[0] + sort = parts[1] + if sort in labels: + query = query.order_by(func(sort)) + else: + for entity in entities: + if component and entity.__table__.name != component: + continue + if sort in entity.__table__.columns: + try: + attr = getattr(entity, sort) + query = query.order_by(func(attr)) + except AttributeError: + pass + break + return query + + +def escape_like(string, escape_char='*'): + """ + Escapes the string paremeter used in SQL LIKE expressions + + :param string: a string to escape + :param escape_char: escape character + """ + return ( + string + .replace(escape_char, escape_char * 2) + .replace('%', escape_char + '%') + .replace('_', escape_char + '_') + )