{ "info": { "author": "Grok Team", "author_email": "grok-dev@zope.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Zope3", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Database", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Libraries" ], "description": "==========\nmegrok.rdb\n==========\n\nIntroduction\n------------\n\nThe ``megrok.rdb`` package adds powerful relational database support\nto Grok, based on the powerful SQLAlchemy_ library. It makes available\na new ``megrok.rdb.Model`` and ``megrok.rdb.Container`` which behave\nmuch like ones in core Grok, but are instead backed by a relational\ndatabase.\n\n.. _SQLAlchemy: http://www.sqlalchemy.org\n\nIn this document we will show you how to use ``megrok.rdb``.\n\nDeclarative models\n------------------\n\n``megrok.rdb`` uses SQLAlchemy's ORM system, in particular its\ndeclarative extension, almost directly. ``megrok.rdb`` just supplies a\nfew special base classes and directives to make things easier, and a few\nother conveniences that help with integration with Grok.\n\nWe first import the SQLAlchemy bits we'll need later::\n\n >>> from sqlalchemy import Column, ForeignKey\n >>> from sqlalchemy.types import Integer, String\n >>> from sqlalchemy.orm import relation\n\nSQLAlchemy groups database schema information into a unit called\n``MetaData``. The schema can be reflected from the database schema, or\ncan be created from a schema defined in Python. With ``megrok.rdb`` we\ntypically do the latter, from within the content classes that they are\nmapped to using the ORM. We need to have some metadata to associate\nour content classes with.\n\nLet's set up the metadata object::\n\n >>> from megrok import rdb\n >>> metadata = rdb.MetaData()\n\nNow we'll set up a few content classes. We'll have a very simple\nstructure where a (university) department has zero or more courses\nassociated with it. First we'll define a container that can contain\ncourses::\n\n >>> class Courses(rdb.Container):\n ... pass\n\nThat's all. If the ``rdb.key`` directive is not used the key in the\ncontainer will be defined as the (possibly automatically assigned)\nprimary key in the database.\n\nFIXME a hack to make things work in doctests. In some particular setup\nthis hack wasn't needed anymore, but I am unable at this time to\nreestablish this combination of packages::\n\n >>> __file__ = 'foo'\n\nNow we can set up the ``Department`` class. This has the ``courses``\nrelation that links to its courses::\n\n >>> class Department(rdb.Model):\n ... rdb.metadata(metadata)\n ...\n ... id = Column('id', Integer, primary_key=True)\n ... name = Column('name', String(50))\n ... \n ... courses = relation('Course', \n ... backref='department',\n ... collection_class=Courses)\n\nThis is very similar to the way you'd use\n``sqlalchemy.ext.declarative``, but there are a few differences::\n\n* we inherit from ``rdb.Model`` to make this behave like a Grok model.\n\n* We don't need to use ``__tablename__`` to set up the table name. By\n default the table name will be the class name, lowercased, but you\n can override this by using the ``rdb.tablename`` directive.\n\n* we need to make explicit the metadata object that is used. We do\n this in the tests, though in Grok applications it's enough to use\n the ``rdb.metadata`` directive on a module-level to have all rdb\n classes automatically associated with that metadata object.\n\n* we mark that the ``courses`` relation uses the ``Courses`` container\n class we have defined before. This is a normal SQLAlchemy feature,\n it's just we have to use it if we want to use Grok-style containers.\n\nWe finish up our database definition by defining the ``Course``\nclass::\n\n >>> class Course(rdb.Model):\n ... rdb.metadata(metadata)\n ...\n ... id = Column('id', Integer, primary_key=True)\n ... department_id = Column('department_id', Integer, \n ... ForeignKey('department.id'))\n ... name = Column('name', String(50))\n\nWe see here that ``Course`` links back to the department it is in,\nusing a foreign key.\n\nConfiguration\n-------------\n\nWe need to actually grok these objects to have them fully set\nup. Normally grok takes care of this automatically, but in this case\nwe'll need to do it manually.\n\nFirst we grok this package's grokkers::\n\n >>> import grokcore.component.testing\n >>> grokcore.component.testing.grok('megrok.rdb.meta')\n\nNow we can grok the components::\n\n >>> from grokcore.component.testing import grok_component\n >>> grok_component('Courses', Courses)\n True\n >>> grok_component('Department', Department)\n True\n >>> grok_component('Course', Course)\n True\n\nOnce we have our metadata and object relational map defined, we need\nto have a database to actually put these in. While it is possible to\nset up a different database per Grok application, here we will use a\nsingle global database::\n\n >>> TEST_DSN = 'sqlite:///:memory:'\n >>> from z3c.saconfig import EngineFactory\n >>> from z3c.saconfig.interfaces import IEngineFactory\n >>> engine_factory = EngineFactory(TEST_DSN)\n\nWe need to supply the engine factory as a utility. Grok can do this\nautomatically for you using the module-level ``grok.global_utility``\ndirective, like this::\n\n grok.global_utility(engine_factory, provides=IEngineFactory, direct=True)\n\nIn the tests we'll use the component architecture directly::\n\n >>> from zope import component\n >>> component.provideUtility(engine_factory, provides=IEngineFactory)\n\nNow that we've set up an engine, we can set up the SQLAlchemy session\nutility::\n\n >>> from z3c.saconfig import GloballyScopedSession\n >>> from z3c.saconfig.interfaces import IScopedSession\n >>> scoped_session = GloballyScopedSession()\n\nWith Grok, we'd register it like this::\n\n grok.global_utility(scoped_session, provides=IScopedSession, direct=True)\n\nBut again we'll just register it directly for the tests::\n\n >>> component.provideUtility(scoped_session, provides=IScopedSession)\n\nWe now need to create the tables we defined in our database. We can do this\nonly when the engine is first created, so we set up a handler for it::\n\n >>> from z3c.saconfig.interfaces import IEngineCreatedEvent\n >>> @component.adapter(IEngineCreatedEvent)\n ... def engine_created(event):\n ... rdb.setupDatabase(metadata)\n >>> component.provideHandler(engine_created)\n\nUsing the database\n------------------\n\nNow all that is out the way, we can use the ``rdb.Session`` object to make\na connection to the database.\n \n >>> session = rdb.Session()\n\nLet's now create a database structure. We have a department of philosophy::\n\n >>> philosophy = Department(name=\"Philosophy\")\n\nWe need to manually add it to the database, as we haven't defined a\nparticular ``departments`` container in our database::\n\n >>> session.add(philosophy)\n\nThe philosophy department has a number of courses::\n\n >>> logic = Course(name=\"Logic\")\n >>> ethics = Course(name=\"Ethics\")\n >>> metaphysics = Course(name=\"Metaphysics\")\n >>> session.add_all([logic, ethics, metaphysics])\n\nWe'll add them to the philosophy department's courses container. Since\nwe want to leave it up to the database what the key will be, we will\nuse the special ``set`` method that ``rdb.Container`` objects have to\nadd the objects::\n\n >>> philosophy.courses.set(logic)\n >>> philosophy.courses.set(ethics)\n >>> philosophy.courses.set(metaphysics)\n\nWe can now verify that the courses are there::\n\n >>> for key, value in sorted(philosophy.courses.items()):\n ... print key, value.name, value.department.name\n 1 Logic Philosophy\n 2 Ethics Philosophy\n 3 Metaphysics Philosophy\n\nAs you can see, the automatically generated primary key is also used\nas the container key now.\n\nThe keys to the container are always integer, even if we're dealing with\na primary key::\n\n >>> philosophy.courses['1'].name\n 'Logic'\n\n >>> philosophy.courses.get('1').name\n 'Logic'\n\nCustom key with ``rdb.key``\n---------------------------\n\nLet's now set up a different attribute to use as the container key.\nWe will use the ``name`` attribute of the course.\n\nWe'll set up the data model again, this time with a ``rdb.key`` on the\n``Courses`` class::\n\n >>> metadata = rdb.MetaData()\n\n >>> class Courses(rdb.Container):\n ... rdb.key('name')\n\n >>> class Department(rdb.Model):\n ... rdb.metadata(metadata)\n ...\n ... id = Column('id', Integer, primary_key=True)\n ... name = Column('name', String(50))\n ... \n ... courses = relation('Course', \n ... backref='department',\n ... collection_class=Courses)\n\n >>> class Course(rdb.Model):\n ... rdb.metadata(metadata)\n ...\n ... id = Column('id', Integer, primary_key=True)\n ... department_id = Column('department_id', Integer, \n ... ForeignKey('department.id'))\n ... name = Column('name', String(50))\n\nWe grok these new classes::\n\n >>> grok_component('Courses', Courses)\n True\n >>> grok_component('Department', Department)\n True\n >>> grok_component('Course', Course)\n True\n\nWe don't need to change the engine, as the underlying relational\ndatabase has remained the same. Let's set up another faculty with some\ndepartments::\n\n >>> physics = Department(name=\"Physics\")\n >>> session.add(physics)\n >>> quantum = Course(name=\"Quantum Mechanics\")\n >>> relativity = Course(name=\"Relativity\")\n >>> high_energy = Course(name=\"High Energy\")\n >>> session.add_all([quantum, relativity, high_energy])\n\nWe'll now add these departments to the physics faculty::\n\n >>> physics.courses.set(quantum)\n >>> physics.courses.set(relativity)\n >>> physics.courses.set(high_energy)\n\nWe can now verify that the courses are there, with the names as the keys::\n\n >>> for key, value in sorted(physics.courses.items()):\n ... print key, value.name, value.department.name\n High Energy High Energy Physics\n Quantum Mechanics Quantum Mechanics Physics\n Relativity Relativity Physics\n\nCustom query container\n----------------------\n\nSometimes we want to expose objects as a (read-only) container based\non a query, not a relation. This is useful when constructing an\napplication and you need a \"starting point\", a root object that\nlaunches into SQLAlchemy-mapped object that itself is not directly\nmanaged by SQLAlchemy.\n\nWe can construct such a special container by subclassing from ``rdb.QueryContainer`` and implementing\nthe special ``query`` method::\n\n >>> class MyQueryContainer(rdb.QueryContainer):\n ... def query(self):\n ... return session.query(Department)\n >>> qc = MyQueryContainer()\n\nLet's try some common read-only container operations, such as\n``__getitem__``::\n\n >>> qc['1'].name\n u'Philosophy'\n >>> qc['2'].name\n 'Physics'\n\nFIXME Why the unicode difference between u'Philosophy' and 'Physics'?\n\n``__getitem__`` with a ``KeyError``::\n\n >>> qc['3']\n Traceback (most recent call last):\n ...\n KeyError: '3'\n\n``get``::\n\n >>> qc.get('1').name\n u'Philosophy'\n >>> qc.get('3') is None\n True\n >>> qc.get('3', 'foo')\n 'foo'\n\n``__contains__``::\n\n >>> '1' in qc\n True\n >>> '3' in qc\n False\n\n``has_key``::\n\n >>> qc.has_key('1')\n True\n >>> qc.has_key('3')\n False\n\n``len``::\n\n >>> len(qc)\n 2\n\n``values``::\n\n >>> sorted([v.name for v in qc.values()])\n [u'Philosophy', 'Physics']\n\nThe parents of all the values are the query container::\n\n >>> [v.__parent__ is qc for v in qc.values()]\n [True, True]\n >>> sorted([v.__name__ for v in qc.values()])\n [u'1', u'2']\n\n``keys``::\n\n >>> sorted([key for key in qc.keys()])\n [u'1', u'2']\n\n``items``::\n\n >>> sorted([(key, value.name) for (key, value) in qc.items()])\n [(u'1', u'Philosophy'), (u'2', 'Physics')]\n\n >>> [value.__parent__ is qc for (key, value) in qc.items()]\n [True, True]\n >>> sorted([value.__name__ for (key, value) in qc.items()])\n [u'1', u'2']\n\n``__iter__``::\n \n >>> result = []\n >>> for key in qc:\n ... result.append(key)\n >>> sorted(result)\n [u'1', u'2']\n\nConverting results of QueryContainer\n------------------------------------\n\nSometimes it's useful to convert (or modify) the output of the query\nto something else before they appear in the container. You can implement\nthe ``convert`` method to do so. It takes the individual value resulting\nfrom the value and should return the converted value::\n\n >>> class ConvertingQueryContainer(rdb.QueryContainer):\n ... def query(self):\n ... return session.query(Department)\n ... def convert(self, value):\n ... return SpecialDepartment(value.id)\n\n >>> class SpecialDepartment(object):\n ... def __init__(self, id):\n ... self.id = id\n\n >>> qc = ConvertingQueryContainer()\n\nLet's now check that all values are ``SpecialDepartment``::\n\n >>> isinstance(qc['1'], SpecialDepartment)\n True\n >>> isinstance(qc['2'], SpecialDepartment)\n True\n\nKeyError still works::\n\n >>> qc['3']\n Traceback (most recent call last):\n ...\n KeyError: '3'\n\n``get``::\n\n >>> isinstance(qc.get('1'), SpecialDepartment)\n True\n >>> qc.get('3') is None\n True\n >>> qc.get('3', 'foo')\n 'foo'\n\n``values``::\n\n >>> [isinstance(v, SpecialDepartment) for v in qc.values()]\n [True, True]\n\nThe parents of all the values are the query container::\n\n >>> [v.__parent__ is qc for v in qc.values()]\n [True, True]\n >>> sorted([v.__name__ for v in qc.values()])\n [u'1', u'2']\n\n``items``::\n\n >>> sorted([(key, isinstance(value, SpecialDepartment)) for (key, value) in qc.items()])\n [(u'1', True), (u'2', True)]\n\n >>> [value.__parent__ is qc for (key, value) in qc.items()]\n [True, True]\n >>> sorted([value.__name__ for (key, value) in qc.items()])\n [u'1', u'2']\n \nCustomizing QueryContainer further\n----------------------------------\n\nSometimes it's useful to define a custom keyfunc and custom method to\nretrieve the key from the database - these usually are implemented\ntogether::\n\n >>> class KeyfuncQueryContainer(rdb.QueryContainer):\n ... def query(self):\n ... return session.query(Department)\n ... def keyfunc(self, value):\n ... return 'd' + unicode(value.id)\n ... def dbget(self, key):\n ... if not key.startswith('d'):\n ... return None\n ... return self.query().get(key[1:])\n\n >>> qc = KeyfuncQueryContainer()\n >>> qc.keys()\n [u'd1', u'd2']\n >>> qc[u'd1'].id\n 1\n\n\n=======\nCHANGES\n=======\n\n0.12 (2011-02-02)\n-----------------\n\n- Update dependencies and imports to work with Grok 1.2 and 1.3.\n\n\n0.11 (2010-02-22)\n-----------------\n\n- Added a LICENSE.txt file.\n\n- Added setupDatabaseSkipCreate. This allows setting up the database\n without trying to create any tables, just reflection.\n\n0.10 (2009-09-18)\n-----------------\n\n- Added to SQLAlchemy to zope.schema adapters so that most of the types in\n sqlalchemy.types are covered.\n\n- Import schema_from_model into ``megrok.rdb`` package namespace.\n\n- Update buildout to use Grok 1.0b2 versions.\n\n- Added a test that demonstrates a common initialization pattern using\n `rdb.setupDatabase`` in a ``IEngineCreatedEvent`` subscriber.\n\n0.9.1 (2009-08-14)\n------------------\n\n- megrok.rdb 0.9 accidentally had zip_safe set to True, which resulted\n in a dud release as its ZCML wouldn't be loaded. Set zip_safe to\n False.\n\n0.9 (2009-08-14)\n----------------\n\n- Initial public release.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "UNKNOWN", "keywords": "rdb relational sqlalchemy grok database", "license": "ZPL", "maintainer": null, "maintainer_email": null, "name": "megrok.rdb", "package_url": "https://pypi.org/project/megrok.rdb/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/megrok.rdb/", "project_urls": { "Download": "UNKNOWN", "Homepage": "UNKNOWN" }, "release_url": "https://pypi.org/project/megrok.rdb/0.12/", "requires_dist": null, "requires_python": null, "summary": "SQLAlchemy based RDB support for Grok.", "version": "0.12" }, "last_serial": 794650, "releases": { "0.10": [ { "comment_text": "", "digests": { "md5": "d99b3353f6cdabea86a75c14548fb625", "sha256": "e20baee3b9a99d4bac8b408fc2c41df87e75cba104f10a32aa1202d56249b144" }, "downloads": -1, "filename": "megrok.rdb-0.10.tar.gz", "has_sig": false, "md5_digest": "d99b3353f6cdabea86a75c14548fb625", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20440, "upload_time": "2009-09-18T13:59:59", "url": "https://files.pythonhosted.org/packages/ee/50/bcbf7624db84f9f2e37ae90cb81c1e93293fc81d179f2c489826e168bad4/megrok.rdb-0.10.tar.gz" } ], "0.11": [ { "comment_text": "", "digests": { "md5": "4838d16725aed78e31782e96f947f960", "sha256": "dd22f201ac6d943cc54344bdd02b4cc9ebf4d604ae997dcf07a47d2043e1243f" }, "downloads": -1, "filename": "megrok.rdb-0.11.tar.gz", "has_sig": false, "md5_digest": "4838d16725aed78e31782e96f947f960", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25406, "upload_time": "2010-02-22T18:52:07", "url": "https://files.pythonhosted.org/packages/6e/d0/8f71b8f3e104379dec26a1d7fcd9df655e4aa61f7ca8aba4010fac84496c/megrok.rdb-0.11.tar.gz" } ], "0.12": [ { "comment_text": "", "digests": { "md5": "1ea582759d7ce4ff8e6059565bf3f41e", "sha256": "09387d5a55caa35f206886b06b50180e030d109d8514177024d0272dacc30735" }, "downloads": -1, "filename": "megrok.rdb-0.12.tar.gz", "has_sig": false, "md5_digest": "1ea582759d7ce4ff8e6059565bf3f41e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21369, "upload_time": "2011-02-02T23:30:51", "url": "https://files.pythonhosted.org/packages/96/16/f7622de90588318777900c8ea483a8dbde6f7d2ddd1376524380d079af5b/megrok.rdb-0.12.tar.gz" } ], "0.9": [ { "comment_text": "", "digests": { "md5": "8ff779e8e20dda27a2b0f02feb2f5e69", "sha256": "45151848ad740d08ec25c0a54fcf141c05d8e72fb2503d593d171c88a8af0a04" }, "downloads": -1, "filename": "megrok.rdb-0.9.tar.gz", "has_sig": false, "md5_digest": "8ff779e8e20dda27a2b0f02feb2f5e69", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23286, "upload_time": "2009-08-14T18:19:40", "url": "https://files.pythonhosted.org/packages/28/47/c6733e943fea7f10654fe5d1bea7cd7d30ae476e879735e6a2dab3d8ae23/megrok.rdb-0.9.tar.gz" } ], "0.9.1": [ { "comment_text": "", "digests": { "md5": "cfd4c75b5ace9075c15433bec49afb0a", "sha256": "05495d4a06ecfafa4d5fb5a509d065902f6c829b785042891890cb8c0b701f51" }, "downloads": -1, "filename": "megrok.rdb-0.9.1.tar.gz", "has_sig": false, "md5_digest": "cfd4c75b5ace9075c15433bec49afb0a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23385, "upload_time": "2009-08-14T18:31:07", "url": "https://files.pythonhosted.org/packages/1e/69/afecd4e2d27a3ae8ccb0c540cc188521d336e15649ed0ccab0ec4cef3703/megrok.rdb-0.9.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "1ea582759d7ce4ff8e6059565bf3f41e", "sha256": "09387d5a55caa35f206886b06b50180e030d109d8514177024d0272dacc30735" }, "downloads": -1, "filename": "megrok.rdb-0.12.tar.gz", "has_sig": false, "md5_digest": "1ea582759d7ce4ff8e6059565bf3f41e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21369, "upload_time": "2011-02-02T23:30:51", "url": "https://files.pythonhosted.org/packages/96/16/f7622de90588318777900c8ea483a8dbde6f7d2ddd1376524380d079af5b/megrok.rdb-0.12.tar.gz" } ] }