{ "info": { "author": "Branko Vukelic", "author_email": "branko@brankovukelic.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License" ], "description": "========\nDocument\n========\n\nDocument is a simple wrapper for dicts that provides an object-oriented\ninterface for accessing keys, as well as the ability to add metadata and\nutility functions to your data. The primary purpose of the ``Document`` class\nis to make working with PyMongo data easier, but it is in no way restricted to\nthis use case. It has no dependencies outside of Python's standard library.\n\nDocument is released under MIT license.\n\nInstallation\n============\n\nDocument can be installed from PyPI::\n\n easy_install document\n\nor::\n\n pip install document\n\nYou can also simply download the ``document`` module and add it to your\nproject.\n\nBasics\n======\n\nLet's first take a look at the constructor. The ``Document`` constructor takes\nany number of keyword argument which are stored as a dict internally. ::\n\n >>> from document import Document\n >>> my_doc = Document(foo='bar', baz=12)\n\nThe dictionary keys can be accessed either as properties or keys::\n\n >>> my_doc.foo\n 'bar'\n >>> my_doc['baz']\n 12\n\nWhen using property access, you can also set new keys::\n\n >>> my_doc.bar = 1\n\nIf you access a missing property, you will get a ``KeyError`` instead of\n``AttributeError`` because, under the hood, we are looking up dictionary keys\nrather than attributes. ::\n\n >>> my_doc.bogus\n Traceback (most recent call last):\n ....\n KeyError: 'bogus'\n\nThis difference is worth noting if you are a practicioner of EAFP_.\n\nUnlike normal Python dictionaries, key access can drill down multiple levels.\nConsider this example::\n\n >>> another_doc = Document(foo={'bar': 'baz'})\n >>> another_doc['foo.bar']\n 'baz'\n\nAs you can see, using a period in the key name will give us access to the\nnested dict's key. For breviti, we will call such keys 'multipart' keys.\n\nThe multipart keys also work when setting values::\n\n >>> another_doc['foo.bar'] = 'fam'\n >>> another_doc.foo\n {'bar': 'fam'}\n\nYou can also use the ``get()`` method with the multipart keys. ::\n\n >>> another_doc.get('foo.bar')\n 'fam'\n >>> another_doc.get('foo.baz')\n None\n\nTesting for existence of a key works with multipart keys as well::\n\n >>> 'foo.bar' in another_doc\n True\n >>> 'foo.baz' in another_doc\n False\n\nBecause of the multipart keys, you cannot use periods in your keys. Those will\nsimply become inaccessible through the normal interface. You can still access\nthem through the private ``_document`` key, but that is not recommended, since\nthe private property is an implementation detail and may be renamed or removed\nin future releases.\n\nAlthough ``Document`` sports the full array of dict methods like ``pop()`` and\n``items()``, they don't work with mutlipart keys but only with top-level keys.\n\nApart from dict methods, ``Document`` implements a few non-standard methods.\nOne of them is ``slice()`` which allows you to get a dict containing a subset\nof the keys. ::\n\n >>> a_doc = Document(foo=1, bar=2, baz=3)\n >>> a_doc.slice('foo', 'baz')\n {'foo': 1, 'baz': 3}\n\nTo get back the full dict with all keys, use the ``to_dict()`` method::\n\n >>> a_doc.to_dict()\n {'foo': 1, 'bar': 2, 'baz': 3}\n\nNote that ``to_dict()`` always returns a copy of the internal dict, not a\nreference to it. Any modification you do to the dict returned by ``to_dict()``\nwill not reflect on whatever is stored in the document.\n\nFor convenience, and for Python purists, the ``Document`` object provides a\n``from_dict()`` method that returns a new document from a dict. ::\n\n >>> b_doc = Document.from_dict({'foo': 'bar'})\n >>> b_doc.foo\n 'bar'\n\nIf you don't care about purity, you can always use the ``**`` magic and the\nconstructor. ::\n\n >>> b_doc = Document(**{'foo': 'bar'})\n\nThe main difference between using ``from_dict`` and the ``**`` magic is the\ntype of the keys that end up in the dict. When you use the magic (and keyword\narguments for that matter), the keys all become strings (in Python 2.x),\nwhereas unicode keys can be preserved when using ``from_dict()`` (and also the\n``update()`` method).\n\nExtending\n=========\n\nNow you might be wondering why you need a whole class to deal with dicts when\ndicts work perfectly fine in Python. That's a valid question. The main\nmotivation behind ``Document`` was to allow developers to define custom methods\nand especially properties that would be separate from the data, but still\naccessible using a similar interface. What this allows us is to have ultitiy\nmethods and metadata attached to our data, that are not serialized and/or saved\ninto the database.\n\nTo demonstrate this we will create a custom ``User`` document.\n\nTo create such a document, we first subclass the ``Document`` class. This is\ngenerally the intended purpose of the ``Document`` class, and you should always\nsubclass it and add new properties. If you feel you don't need to subclass, you\ncan probably get away with a plain ``dict``.\n\nBack to our example, let's say we have a user document that should have an\n``authenticated`` flag that is, for obvious reasons, only used during a\nrequest-response cycle, and not saved to the database. We also want to have a\nmethod that will check passwords, as well as one that will set it. The subclass\nmight look something like this::\n\n class User(Document):\n authenticated = False\n\n def check_password(self, password):\n return encrypt(password) == self.password\n\n def set_password(self, password):\n self.password = encrypt(password)\n\nNow we can, say, retrieve a dict from a database and convert it to a user\ndocument (using some imaginary database and request API in this example)::\n\n user_dict = db.users.get(username='foo')\n password = request.params['password']\n user = User.from_dict(user_dict)\n if user.check_password(password):\n user.authenticated = True\n session['user'] = user\n return 'success!'\n return 'wrong username or password'\n\nSuppose the database expects us to save a new record by passing it a dict\nrepresenting the record's data (which is how PyMongo works, for example). \nLet's store a new user::\n\n username = request.params['username']\n password = request.params['password']\n user = User(usernam=username)\n user.set_password(password)\n db.users.save(user.to_dict())\n\nBy using the ``to_dict()`` method, we avoid having to deal with\n``authenticated`` property, as well as the two methods we have defined on the\n``User`` document. Only the username and encrypted passwords are saved. This\nprovides a clean separation of what we consider metadata and actual data.\n\nThis separation has other consequences. Comparing two records with different\nmetadata will only compare the actual data. For example::\n\n >>> class FooDoc(Document):\n ... meta = True\n >>> foo1 = FooDoc(foo=1)\n >>> foo2 = FooDoc(foo=1)\n >>> foo1.meta = False\n >>> foo1 == foo2\n True\n\nDespite the two documents having different values for the ``meta`` property,\nthey are still considered equal because the actual data is equal.\n\nAnother thing to note that, because we can have custom properties, and also\nassign dictionary keys using properties, only the properties that are defined\non the class can actually be set as properties, and everything else is\nconsidered a dictionary key. To demonstrate this, we will use the ``FooDoc``\nclass defined before. ::\n\n >>> foo1 = FooDoc(foo=1)\n >>> foo1.meta = True # Sets the ``meta`` property\n >>> foo1.metadata = 'bar' # Creates an actual dict key called ``metadata``\n >>> foo1.to_dict()\n {'foo': 1, 'metadata': 'bar'}\n\nAPI documentation\n=================\n\nThe whole ``document`` module is a little under 440 lines of code including\ninline documentation and doctests. Therefore, you are advised to look at the\nsource code for in-depth API documentation. All examples in the inline\ndocumentation double as unit tests so they are virtually guaranteed to work as\ndocumented.\n\nReporting bugs\n==============\n\nReport all bugs to the `BitBucket issue tracker`_\n\n.. _EAFP: http://docs.python.org/2/glossary.html#term-eafp\n.. _BitBucket issue tracker: https://bitbucket.org/brankovukelic/document/issues", "description_content_type": null, "docs_url": null, "download_url": "https://bitbucket.org/brankovukelic/document/downloads", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://bitbucket.org/brankovukelic/document/", "keywords": null, "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "document", "package_url": "https://pypi.org/project/document/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/document/", "project_urls": { "Download": "https://bitbucket.org/brankovukelic/document/downloads", "Homepage": "https://bitbucket.org/brankovukelic/document/" }, "release_url": "https://pypi.org/project/document/1.0/", "requires_dist": null, "requires_python": null, "summary": "Wraps dicts in an object for convenient document management", "version": "1.0" }, "last_serial": 1019798, "releases": { "1.0": [ { "comment_text": "", "digests": { "md5": "a21df4d1928a9df55fab7c09b66dfbda", "sha256": "6f6381266d3c7ca3d9cd0a835b9721648346af7bd2d66a2452f8eb08c01bc4a5" }, "downloads": -1, "filename": "document-1.0.tar.gz", "has_sig": false, "md5_digest": "a21df4d1928a9df55fab7c09b66dfbda", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8657, "upload_time": "2014-03-05T10:54:44", "url": "https://files.pythonhosted.org/packages/7d/14/2230c93dbabaa3c8d6db8fe781f66bbfc48d3232bf4dec4defe27f878309/document-1.0.tar.gz" }, { "comment_text": "", "digests": { "md5": "c709a68db8bc5b9ccf952ba5cd9cd26e", "sha256": "077d79b7d82f691820dcd98e608dfc2a3a64cd5299798ad05c6aa52f4982e730" }, "downloads": -1, "filename": "document-1.0.zip", "has_sig": false, "md5_digest": "c709a68db8bc5b9ccf952ba5cd9cd26e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12085, "upload_time": "2014-03-05T10:54:42", "url": "https://files.pythonhosted.org/packages/ad/fb/eaf5723845151da6aab8bd051bd46df8fa36b9ffb201e3824bf0cfd9ea34/document-1.0.zip" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "a21df4d1928a9df55fab7c09b66dfbda", "sha256": "6f6381266d3c7ca3d9cd0a835b9721648346af7bd2d66a2452f8eb08c01bc4a5" }, "downloads": -1, "filename": "document-1.0.tar.gz", "has_sig": false, "md5_digest": "a21df4d1928a9df55fab7c09b66dfbda", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8657, "upload_time": "2014-03-05T10:54:44", "url": "https://files.pythonhosted.org/packages/7d/14/2230c93dbabaa3c8d6db8fe781f66bbfc48d3232bf4dec4defe27f878309/document-1.0.tar.gz" }, { "comment_text": "", "digests": { "md5": "c709a68db8bc5b9ccf952ba5cd9cd26e", "sha256": "077d79b7d82f691820dcd98e608dfc2a3a64cd5299798ad05c6aa52f4982e730" }, "downloads": -1, "filename": "document-1.0.zip", "has_sig": false, "md5_digest": "c709a68db8bc5b9ccf952ba5cd9cd26e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12085, "upload_time": "2014-03-05T10:54:42", "url": "https://files.pythonhosted.org/packages/ad/fb/eaf5723845151da6aab8bd051bd46df8fa36b9ffb201e3824bf0cfd9ea34/document-1.0.zip" } ] }