From 348214e2e9758926a4b3659d0e924fa56a4783be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=9A=D0=B0=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D0=B2?= Date: Sat, 9 Feb 2019 00:01:02 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0=20sqlite.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=20=D0=B2=D0=B5=D1=80=D1=81?= =?UTF-8?q?=D0=B8=D0=B8=200.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .hgignore | 1 + Makefile | 5 ++- flatfilecms/__init__.py | 1 + flatfilecms/models/__init__.py | 77 +++++++++++++++++++++++++++++++++ flatfilecms/models/meta.py | 16 +++++++ flatfilecms/models/pagemodel.py | 17 ++++++++ flatfilecms/pshell.py | 13 ++++++ flatfilecms/tests.py | 66 ++++++++++++++++++++++++++++ pytest.ini | 3 ++ setup.py | 10 +++-- 10 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 flatfilecms/models/__init__.py create mode 100644 flatfilecms/models/meta.py create mode 100644 flatfilecms/models/pagemodel.py create mode 100644 flatfilecms/pshell.py create mode 100644 flatfilecms/tests.py create mode 100644 pytest.ini diff --git a/.hgignore b/.hgignore index 7abd490..0222ec8 100644 --- a/.hgignore +++ b/.hgignore @@ -39,6 +39,7 @@ pip-log.txt .coverage .tox nosetests.xml +.pytest_cache # Translations *.mo diff --git a/Makefile b/Makefile index c052994..cca54e2 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ -all: bdist +all: test bdist bdist: python3 setup.py sdist bdist_wheel + +test: + pytest -q diff --git a/flatfilecms/__init__.py b/flatfilecms/__init__.py index 5bba17f..2a5aa4c 100644 --- a/flatfilecms/__init__.py +++ b/flatfilecms/__init__.py @@ -64,6 +64,7 @@ def main(global_config, **settings): config = Configurator(settings=settings, root_factory=factory) + config.include('.models') config.include('pyramid_jinja2') config.include('.routes') config.scan() diff --git a/flatfilecms/models/__init__.py b/flatfilecms/models/__init__.py new file mode 100644 index 0000000..77623d9 --- /dev/null +++ b/flatfilecms/models/__init__.py @@ -0,0 +1,77 @@ +from sqlalchemy import engine_from_config +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import configure_mappers +import zope.sqlalchemy + +# import or define all models here to ensure they are attached to the +# Base.metadata prior to any initialization routines +from .pagemodel import PageModel # flake8: noqa + +# run configure_mappers after defining all of the models to ensure +# all relationships can be setup +configure_mappers() + + +def get_engine(settings, prefix='sqlalchemy.'): + return engine_from_config(settings, prefix) + + +def get_session_factory(engine): + factory = sessionmaker() + factory.configure(bind=engine) + return factory + + +def get_tm_session(session_factory, transaction_manager): + """ + Get a ``sqlalchemy.orm.Session`` instance backed by a transaction. + + This function will hook the session to the transaction manager which + will take care of committing any changes. + + - When using pyramid_tm it will automatically be committed or aborted + depending on whether an exception is raised. + + - When using scripts you should wrap the session in a manager yourself. + For example:: + + import transaction + + engine = get_engine(settings) + session_factory = get_session_factory(engine) + with transaction.manager: + dbsession = get_tm_session(session_factory, transaction.manager) + + """ + dbsession = session_factory() + zope.sqlalchemy.register( + dbsession, transaction_manager=transaction_manager) + return dbsession + + +def includeme(config): + """ + Initialize the model for a Pyramid app. + + Activate this setup using ``config.include('db.models')``. + + """ + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' + + # use pyramid_tm to hook the transaction lifecycle to the request + config.include('pyramid_tm') + + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + + session_factory = get_session_factory(get_engine(settings)) + config.registry['dbsession_factory'] = session_factory + + # make request.dbsession available for use in Pyramid + config.add_request_method( + # r.tm is the transaction manager used by pyramid_tm + lambda r: get_tm_session(session_factory, r.tm), + 'dbsession', + reify=True + ) diff --git a/flatfilecms/models/meta.py b/flatfilecms/models/meta.py new file mode 100644 index 0000000..02285b3 --- /dev/null +++ b/flatfilecms/models/meta.py @@ -0,0 +1,16 @@ +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.schema import MetaData + +# Recommended naming convention used by Alembic, as various different database +# providers will autogenerate vastly different names making migrations more +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html +NAMING_CONVENTION = { + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" +} + +metadata = MetaData(naming_convention=NAMING_CONVENTION) +Base = declarative_base(metadata=metadata) diff --git a/flatfilecms/models/pagemodel.py b/flatfilecms/models/pagemodel.py new file mode 100644 index 0000000..bf866c3 --- /dev/null +++ b/flatfilecms/models/pagemodel.py @@ -0,0 +1,17 @@ +from sqlalchemy import ( + Column, + Index, + String, + Text, +) + +from .meta import Base + + +class PageModel(Base): + __tablename__ = 'pages' + url = Column(String(2000), primary_key=True) + title = Column(Text) + + +Index('page_index', PageModel.url, unique=True, mysql_length=255) diff --git a/flatfilecms/pshell.py b/flatfilecms/pshell.py new file mode 100644 index 0000000..b0847ee --- /dev/null +++ b/flatfilecms/pshell.py @@ -0,0 +1,13 @@ +from . import models + + +def setup(env): + request = env['request'] + + # start a transaction + request.tm.begin() + + # inject some vars into the shell builtins + env['tm'] = request.tm + env['dbsession'] = request.dbsession + env['models'] = models diff --git a/flatfilecms/tests.py b/flatfilecms/tests.py new file mode 100644 index 0000000..09ca294 --- /dev/null +++ b/flatfilecms/tests.py @@ -0,0 +1,66 @@ +import unittest + +from pyramid import testing + +import transaction + + +def dummy_request(dbsession): + return testing.DummyRequest(dbsession=dbsession) + + +class BaseTest(unittest.TestCase): + def setUp(self): + self.config = testing.setUp(settings={ + 'sqlalchemy.url': 'sqlite:///:memory:' + }) + self.config.include('.models') + settings = self.config.get_settings() + + from .models import ( + get_engine, + get_session_factory, + get_tm_session, + ) + + self.engine = get_engine(settings) + session_factory = get_session_factory(self.engine) + + self.session = get_tm_session(session_factory, transaction.manager) + + def init_database(self): + from .models.meta import Base + Base.metadata.create_all(self.engine) + + def tearDown(self): + from .models.meta import Base + + testing.tearDown() + transaction.abort() + Base.metadata.drop_all(self.engine) + + +# class TestMyViewSuccessCondition(BaseTest): + +# def setUp(self): +# super(TestMyViewSuccessCondition, self).setUp() +# self.init_database() + +# from .models import PageModel + +# model = PageModel(name='one', value=55) +# self.session.add(model) + +# def test_passing_view(self): +# from .views.default import my_view +# info = my_view(dummy_request(self.session)) +# self.assertEqual(info['one'].name, 'one') +# self.assertEqual(info['project'], 'db') + + +# class TestMyViewFailureCondition(BaseTest): + +# def test_failing_view(self): +# from .views.default import my_view +# info = my_view(dummy_request(self.session)) +# self.assertEqual(info.status_int, 500) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..49e7ade --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = flatfilecms +python_files = test*.py diff --git a/setup.py b/setup.py index 43546cc..b7b11e0 100644 --- a/setup.py +++ b/setup.py @@ -11,12 +11,14 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', 'pyramid >= 1.10', - 'pyramid_debugtoolbar', 'pyramid_jinja2', + 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', + 'SQLAlchemy', 'transaction', - 'waitress', + 'zope.sqlalchemy', 'Markdown', 'PyYAML', 'python-frontmatter', @@ -27,13 +29,13 @@ requires = [ tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', + 'pytest >= 3.7.4', 'pytest-cov', ] setup( name='flatfilecms', - version='0.2', + version='0.3', description='flat-file CMS suitable for static site', long_description=README + '\n\n' + CHANGES, classifiers=[ From a906b6a2fcfff274a189f24fdaf33fca191b5e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=9A=D0=B0=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D0=B2?= Date: Sun, 10 Feb 2019 19:57:35 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20sqlalchemy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flatfilecms/resources.py | 3 +++ pytest.ini | 2 +- tests/pages/index.md | 7 +++++ flatfilecms/tests.py => tests/test_db.py | 33 +++++++++++++++++++----- 4 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 tests/pages/index.md rename flatfilecms/tests.py => tests/test_db.py (62%) diff --git a/flatfilecms/resources.py b/flatfilecms/resources.py index 061bb49..2f89e41 100644 --- a/flatfilecms/resources.py +++ b/flatfilecms/resources.py @@ -1,6 +1,8 @@ from pyramid.path import AssetResolver from pathlib import PurePath +from .models import PageModel + def flat(d, path): structure = [] @@ -61,6 +63,7 @@ class Document(object): self.__name__ = name self.__parent__ = parent self.__path__ = path + PageModel(url=name) class Markdown(Document): diff --git a/pytest.ini b/pytest.ini index 49e7ade..18522a6 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] -testpaths = flatfilecms +testpaths = tests python_files = test*.py diff --git a/tests/pages/index.md b/tests/pages/index.md new file mode 100644 index 0000000..5466d95 --- /dev/null +++ b/tests/pages/index.md @@ -0,0 +1,7 @@ +--- +title: Заглушка для тестов БД +description: Тестовая страница +template: onepage +--- + +Hello World! diff --git a/flatfilecms/tests.py b/tests/test_db.py similarity index 62% rename from flatfilecms/tests.py rename to tests/test_db.py index 09ca294..7b662db 100644 --- a/flatfilecms/tests.py +++ b/tests/test_db.py @@ -5,6 +5,7 @@ from pyramid import testing import transaction + def dummy_request(dbsession): return testing.DummyRequest(dbsession=dbsession) @@ -14,10 +15,10 @@ class BaseTest(unittest.TestCase): self.config = testing.setUp(settings={ 'sqlalchemy.url': 'sqlite:///:memory:' }) - self.config.include('.models') + self.config.include('flatfilecms.models') settings = self.config.get_settings() - from .models import ( + from flatfilecms.models import ( get_engine, get_session_factory, get_tm_session, @@ -29,30 +30,48 @@ class BaseTest(unittest.TestCase): self.session = get_tm_session(session_factory, transaction.manager) def init_database(self): - from .models.meta import Base + from flatfilecms.models.meta import Base Base.metadata.create_all(self.engine) def tearDown(self): - from .models.meta import Base + from flatfilecms.models.meta import Base testing.tearDown() transaction.abort() Base.metadata.drop_all(self.engine) +class TestRootLoadSuccessCondition(BaseTest): + def setUp(self): + super(TestRootLoadSuccessCondition, self).setUp() + + from flatfilecms.resources import Root + self.pages = Root('../tests/pages') + + def test_loaded_structure(self): + assert 'index' in self.pages + + def test_db_contains_pages(self): + from flatfilecms.models import PageModel + + index = self.session.query(PageModel).get('/') + + assert index.title == 'Заглушка для тестов БД' + + # class TestMyViewSuccessCondition(BaseTest): # def setUp(self): # super(TestMyViewSuccessCondition, self).setUp() # self.init_database() -# from .models import PageModel +# from flatfilecms.models import PageModel # model = PageModel(name='one', value=55) # self.session.add(model) # def test_passing_view(self): -# from .views.default import my_view +# from flatfilecms.views.default import my_view # info = my_view(dummy_request(self.session)) # self.assertEqual(info['one'].name, 'one') # self.assertEqual(info['project'], 'db') @@ -61,6 +80,6 @@ class BaseTest(unittest.TestCase): # class TestMyViewFailureCondition(BaseTest): # def test_failing_view(self): -# from .views.default import my_view +# from flatfilecms.views.default import my_view # info = my_view(dummy_request(self.session)) # self.assertEqual(info.status_int, 500)