├── .gitignore ├── CHANGELOG ├── tox.ini ├── .travis.yml ├── LICENSE ├── README ├── TODO ├── setup.py ├── tests └── test_paginate_sqlalchemy.py └── paginate_sqlalchemy └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | *.pyc 3 | *.komodoproject 4 | *.egg-info/ 5 | build/ 6 | virtualenv/ 7 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Version 0.2.0 - 2014-08-25 2 | --------------------------- 3 | Second version, fixing some tests. 4 | 5 | Version 0.1.0 - 2012-12-xxx 6 | --------------------------- 7 | First version. 8 | 9 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py34,py35,py36,py37,pypy 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | # Most of these are defaults but if you specify any you can't fall back 7 | # to defaults for others. 8 | basepython = 9 | py27: python2.7 10 | py34: python3.4 11 | py35: python3.5 12 | py36: python3.6 13 | py37: python3.7 14 | pypy: pypy 15 | py2: python2.7 16 | py3: python3.6 17 | deps = 18 | pytest 19 | 20 | commands = 21 | py.test {posargs:tests} 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - python: 2.7 7 | env: TOXENV=py27 8 | - python: 3.4 9 | env: TOXENV=py34 10 | - python: 3.5 11 | env: TOXENV=py35 12 | - python: pypy 13 | env: TOXENV=pypy 14 | - python: 3.6 15 | env: TOXENV=py36 16 | - python: nightly 17 | env: TOXENV=py37 18 | allow_failures: 19 | - env: TOXENV=py37 20 | 21 | install: 22 | - travis_retry pip install tox 23 | 24 | script: 25 | - travis_retry tox 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2012 Christoph Haas 2 | 3 | As of 25-08-2014 Christoph Haas has given this code to Luke Crooks 4 | to maintain, due to not having time to continue 5 | development. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | What is pagination? 2 | --------------------- 3 | This module helps dividing large lists of items into pages. The user is shown one page at a time and 4 | can navigate to other pages. Imagine you are offering a company phonebook and let the user search 5 | the entries. If the search result contains 23 entries but you may want to display no more than 10 6 | entries at once. The first page contains entries 1-10, the second 11-20 and the third 21-23. See the 7 | documentation of the "Page" class for more information. 8 | 9 | What does this module do? 10 | --------------------------- 11 | The *paginate* module supports list-like objects only. If you want to paginate through SQLAlchemy 12 | objects like Select or ORM-mapped objects then use this module. It provides an SqlalchemyOrmPage 13 | class for that purpose. 14 | 15 | How do I use this module with ORM-mapped objects? 16 | --------------------------------------------------- 17 | See the documentation for *paginate.Page* about how to use pagination. Instead of *paginate.Page* 18 | you just use *paginate_sqlalchemy.SqlalchemyOrmPage *with the same parameters as *paginate.Page*. 19 | Assumed that have an ORM class called *Cars*. You would create a query in your SQLAlchemy session:: 20 | 21 | cars_query = session.query(Cars) 22 | 23 | Finally you create a page from this query:: 24 | 25 | page = paginate_sqlalchemy.SqlalchemyOrmPage(cars_query, page=5) 26 | 27 | This *page* object works just like any *paginate.Page* object. 28 | 29 | You can find a complete example in the tests/test_paginate_sqlalchemy.py file of this Python module. 30 | 31 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Support Select objects 2 | 3 | Upload to PyPi 4 | 5 | Announce to pylons-discussion mailing list. 6 | 7 | Write SQLAlchemy tests 8 | 9 | Create test with setup/teardown code for paginate.sqlalchemy.SqlalchemyPage (e.g. sqlite) 10 | 11 | #class TestSQLAlchemyCollectionTypes(unittest.TestCase): 12 | # def setUp(self): 13 | # try: 14 | # import sqlalchemy as sa 15 | # import sqlalchemy.orm as orm 16 | # except ImportError: 17 | # raise SkipTest() 18 | # self.engine = engine = sa.create_engine("sqlite://") # Memory database 19 | # self.sessionmaker = orm.sessionmaker(bind=engine) 20 | # self.metadata = metadata = sa.MetaData(bind=engine) 21 | # self.notes = notes = sa.Table("Notes", metadata, 22 | # sa.Column("id", sa.Integer, primary_key=True)) 23 | # class Note(object): 24 | # pass 25 | # self.Note = Note 26 | # notes.create() 27 | # orm.mapper(Note, notes) 28 | # insert = notes.insert() 29 | # records = [{"id": x} for x in range(1, 101)] 30 | # engine.execute(insert, records) 31 | # 32 | # def tearDown(self): 33 | # import sqlalchemy as sa 34 | # import sqlalchemy.orm as orm 35 | # orm.clear_mappers() 36 | # self.notes.drop() 37 | # 38 | # def test_sqlalchemy_orm(self): 39 | # session = self.sessionmaker() 40 | # q = session.query(self.Note).order_by(self.Note.id) 41 | # page = paginate.Page(q) 42 | # records = list(page) 43 | # eq_(records[0].id, 1) 44 | # eq_(records[-1].id, 20) 45 | 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | import sys, os 4 | 5 | setup( 6 | name='paginate_sqlalchemy', 7 | version='0.3.0', 8 | description="Extension to paginate.Page that supports SQLAlchemy queries", 9 | long_description=""" 10 | This module helps divide up large result sets into pages or chunks. 11 | The user gets displayed one page at a time and can navigate to other pages. 12 | It is especially useful when developing web interfaces and showing the 13 | users only a selection of information at a time. 14 | 15 | This module uses and extends the functionality of the paginate module to 16 | support SQLAlchemy queries. 17 | """, 18 | 19 | # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 20 | classifiers=[ 21 | 'Programming Language :: Python', 22 | 'Programming Language :: Python :: 2', 23 | 'Programming Language :: Python :: 3', 24 | 'Development Status :: 4 - Beta', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Topic :: Software Development :: Libraries :: Python Modules', 28 | ], 29 | keywords='pagination paginate sqlalchemy', 30 | author='Christoph Haas', 31 | author_email='email@christoph-haas.de', 32 | maintainer='Luke Crooks', 33 | maintainer_email='luke@pumalo.org', 34 | install_requires=[ 35 | "sqlalchemy>=0.8.3", 36 | "paginate>=0.4" 37 | ], 38 | url='https://github.com/Pylons/paginate_sqlalchemy', 39 | license='MIT', 40 | packages=find_packages(), 41 | include_package_data=True, 42 | zip_safe=False, 43 | entry_points=""" """, 44 | ) 45 | -------------------------------------------------------------------------------- /tests/test_paginate_sqlalchemy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2007-2012 Christoph Haas 2 | # See the file LICENSE for copying permission. 3 | 4 | """Enhances the paginate.Page class to work with SQLAlchemy objects""" 5 | 6 | import pytest 7 | import sqlalchemy as sa 8 | import sqlalchemy.orm 9 | import paginate_sqlalchemy 10 | 11 | from sqlalchemy.ext.declarative import declarative_base 12 | 13 | Base = declarative_base() 14 | 15 | 16 | class User(Base): 17 | __tablename__ = 'users' 18 | id = sa.Column(sa.Integer, primary_key=True) 19 | name = sa.Column(sa.String) 20 | 21 | 22 | @pytest.fixture(scope='function') 23 | def db_engine(): 24 | engine = sa.create_engine('sqlite:///:memory:', echo=True) 25 | Base.metadata.create_all(engine) 26 | return engine 27 | 28 | 29 | @pytest.fixture(scope='function') 30 | def db_session(db_engine): 31 | return sqlalchemy.orm.sessionmaker(bind=db_engine)() 32 | 33 | 34 | @pytest.fixture(scope='function') 35 | def base_data(db_session): 36 | users = [] 37 | for i in range(1000): 38 | users.append({'id': i, 'name': 'user_{}'.format(i)}) 39 | db_session.execute(User.__table__.insert(), users) 40 | db_session.commit() 41 | 42 | 43 | @pytest.mark.usefixtures("base_data") 44 | class TestSqlalchemyPage(object): 45 | def test_orm(self, db_session): 46 | orm_query = db_session.query(User) 47 | page = paginate_sqlalchemy.SqlalchemyOrmPage( 48 | orm_query, page=8, db_session=db_session) 49 | assert page.item_count == 1000 50 | assert page.first_item == 141 51 | assert page.last_item == 160 52 | assert page.page_count == 50 53 | 54 | def test_select(self, db_session): 55 | users_table = User.__table__ 56 | select_query = sqlalchemy.sql.select([users_table]) 57 | page = paginate_sqlalchemy.SqlalchemySelectPage( 58 | db_session, select_query, page=8) 59 | assert page.item_count == 1000 60 | assert page.first_item == 141 61 | assert page.last_item == 160 62 | assert page.page_count == 50 63 | -------------------------------------------------------------------------------- /paginate_sqlalchemy/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2007-2012 Christoph Haas 2 | # See the file LICENSE for copying permission. 3 | 4 | """Enhances the paginate.Page class to work with SQLAlchemy objects""" 5 | 6 | import paginate 7 | 8 | 9 | class SqlalchemyOrmWrapper(object): 10 | """Wrapper class to access elements of an SQLAlchemy ORM query result.""" 11 | 12 | def __init__(self, obj): 13 | self.obj = obj 14 | 15 | def __getitem__(self, range): 16 | if not isinstance(range, slice): 17 | raise Exception("__getitem__ without slicing not supported") 18 | return self.obj[range] 19 | 20 | def __len__(self): 21 | return self.obj.count() 22 | 23 | 24 | class SqlalchemyOrmPage(paginate.Page): 25 | """A pagination page that deals with SQLAlchemy ORM objects. 26 | 27 | See the documentation on paginate.Page for general information on how to work 28 | with instances of this class.""" 29 | 30 | # This class just subclasses paginate.Page which contains all the functionality. 31 | # It just instantiates the class with a "wrapper_class" argument telling it how the 32 | # collection can be accessed. 33 | def __init__(self, *args, **kwargs): 34 | super(SqlalchemyOrmPage, self).__init__( 35 | *args, wrapper_class=SqlalchemyOrmWrapper, **kwargs) 36 | 37 | 38 | def sql_wrapper_factory(db_session=None): 39 | class SqlalchemySelectWrapper(object): 40 | """Wrapper class to access elements of an SQLAlchemy SELECT query.""" 41 | 42 | def __init__(self, obj): 43 | self.obj = obj 44 | self.db_session = db_session 45 | 46 | def __getitem__(self, range): 47 | if not isinstance(range, slice): 48 | raise Exception("__getitem__ without slicing not supported") 49 | # value for offset 50 | offset_v = range.start 51 | limit = range.stop - range.start 52 | select = self.obj.limit(limit).offset(offset_v) 53 | return self.db_session.execute(select).fetchall() 54 | 55 | def __len__(self): 56 | return self.db_session.execute(self.obj.alias().count()).scalar() 57 | 58 | return SqlalchemySelectWrapper 59 | 60 | 61 | class SqlalchemySelectPage(paginate.Page): 62 | """A pagination page that deals with SQLAlchemy Select objects. 63 | 64 | See the documentation on paginate.Page for general information on how to work 65 | with instances of this class.""" 66 | 67 | # This class just subclasses paginate.Page which contains all the functionality. 68 | # It just instantiates the class with a "wrapper_class" argument telling it how the 69 | # collection can be accessed. 70 | def __init__(self, db_session, *args, **kwargs): 71 | """sqlalchemy_connection: SQLAlchemy connection object""" 72 | wrapper = sql_wrapper_factory(db_session) 73 | super(SqlalchemySelectPage, self).__init__( 74 | *args, wrapper_class=wrapper, **kwargs) 75 | --------------------------------------------------------------------------------