{ "info": { "author": "Seperman", "author_email": "sepd@fair.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Software Development" ], "description": "# Polymorphic extension for SQLAlchemy v0.2.6\n\n# Install\n\n`pip install polymorphic-sqlalchemy`\n\n# Why?\n\nImagine if you have a table with some data.\n\n```\n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503Vehicle Table\u2503\n\u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b\n\u2503 1 BMW 3 \u2503\n\u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b\n\u2503 2 Tesla S \u2503\n\u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b\n```\n\n\nAnd now you want to know what the source of each row was. You could start by simply making foreign keys to individual tables that could be the source of your data in the vehicle table. Let's say at this point the source of vehicle could be some organization or some dealer:\n\n\n```\n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503Vehicle Table\u2503 \u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b \u2503 Org Table \u2503\n\u2503 1 BMW 3 \u2503----\u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b\n\u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b \u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503 2 Tesla S \u2503----\u2503Dealer Table \u2503\n\u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b\n```\n\n\nBut once you have many different sources of data, this approach quickly becomes unmanageable.\n\nNow imagine if you are using a microservice architecture so your vehicle model and org model live on the same database but some other microservice has the dealer table.\n\n\n```\n Service 1 \u2591 \u2591 Service 2\n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513 \u2591 \u2591\n\u2503Vehicle Table\u2503 \u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513 \u2591 \u2591\n\u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b \u2503 Org Table \u2503 \u2591 \u2591\n\u2503 1 BMW 3 \u2503----\u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b \u2591 \u2591\n\u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b \u2591 \u2591 \u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503 2 Tesla S \u2503-------------------Client \u2508 network \u2508 \u2508 API-\u2503Dealer Table \u2503\n\u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b \u2591 \u2591 \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b\n \u2591 \u2591\n```\n\n\nThe Polymorphic extension is written to manage relationship such as above. So you can deal with the above situation as if the models were all in the same database in the same service.\n\n# How to use Polymorphic Extension\n\n[Take a look at the tutorial](#tutorial.md)\n\n# Reference\n\nThis package introduces the following fields and helpers:\n\n- `PolyField` : Used to get and set polymorphic objects. It reads and writes to the `[prefix]_id` and `[prefix]_type` fields in the same object.\n\n Example:\n\n ```py\n source_id = Column(String(50), nullable=False)\n source_type = Column(String(50), nullable=False)\n source = PolyField(prefix='source')\n ```\n\n- `NetRelationship` : Used for network backed relationships. Acts similar to SQLAlchemy relationships. Use `NetRelationship` when you have network backed models that SQLAlchemy can not automatically make relationship to them. ONLY used for polymorphic network backed objects. It needs to be used along the `PolyField` and `[prefix]_id` and `[prefix]_type` fields. The convension is to use double underscore between the prefix and class name as the name for this field. For example if the prefix is `source` (simply because we have source_id and source_type fields, then the PolyField should be called `source` too and the NetRelationship field that is pointing to the Dealer Model should be called `[prefix]__[model lower case name]` which is `source__dealer` in this case:\n\n\n ```py\n source_id = Column(String(50), nullable=False)\n source_type = Column(String(50), nullable=False)\n source = PolyField(prefix='source')\n source__dealer = NetRelationship(prefix='source', _class=Dealer)\n ```\n\n- `NetModel` : Used for network backed models. Only for **NON-polymorphic** relationships.\n\n Another field type that this library provides is the NetModel\n\n Example:\n\n ```py\n from polymorphic_sqlalchemy import NetModel\n\n class NetworkModel:\n\n def __init__(self, dealer_id):\n self.dealer_id = dealer_id\n\n # Look at the Dealer definition in multiple relations example in the readme.\n dealer = NetModel(field='dealer_id', _class=Dealer)\n\n\n obj = NetworkModel(1)\n\n dealer1 = Dealer(1)\n assert obj.dealer == dealer1\n ```\n\n- `BaseInitializer` : Used as base class for SQLAlchemy models. It needs to be used in your super classes BEFORE the `db.Model`. For example `class VehicleReferencePrice(BaseInitializer, db.Model)` is correct but `class VehicleReferencePrice(db.Model, BaseInitializer)` is wrong. All it does is that it helps you with instantiation of your SQLAlchemy models so fields are instantiated in the correct order. If you don't use this base class, you need to make sure the SQLAlchemy fields are instantiated BEFORE the non-SQLAlchemy fields. For example `source_id` and `source_type` need to be instantiated BEFORE `source` which is a PolyField.\n\n- `create_polymorphic_base` : creates base class from your data class to be added to your ref classes. Data class is where the `[prefix]_id` and `[prefix]_type]` fields along your PolyField are defined. Ref class[es] are which models that the polymorphic relationship points to. The relationship is automatically created for you by using the output of `create_polymorphic_base` as a base class in your ref class[es].\n\n# Examples\n\nNote: Please take a look at the [tutorial](#tutorial.md) for a step by step guide into the Polymorphic extension.\nAlso you can find actual working models in the [model tests](tests/models.py).\n\n## Single relation:\n\n```py\nfrom polymorphic_sqlalchemy import create_polymorphic_base, PolyField, BaseInitializer\nfrom sqlalchemy import Column, Integer, String\n\n\nclass VehicleReferencePrice(BaseInitializer, db.Model):\n\n __tablename__ = \"vehicle_reference_prices\"\n\n id = Column(Integer, primary_key=True, autoincrement=True)\n source_id = Column(String(50), nullable=False)\n source_type = Column(String(50), nullable=False)\n source = PolyField(prefix='source')\n\n\nHasVehicleReferencePrices = create_polymorphic_base(data_class=VehicleReferencePrice,\n data_class_attr='source')\n\n\nclass FairEstimatedValue(BaseInitializer, db.Model, HasVehicleReferencePrices):\n __tablename__ = \"fair_estimated_value\"\n id = Column(Integer, primary_key=True, autoincrement=True)\n\nclass SomeRecord(BaseInitializer, db.Model, HasVehicleReferencePrices):\n __tablename__ = \"manheim_records\"\n id = Column(Integer, primary_key=True, autoincrement=True)\n```\n\n## multiple relations\n\n```py\nfrom polymorphic_sqlalchemy import create_polymorphic_base, Relation, PolyField, NetRelationship\nfrom sqlalchemy import Column, Integer, String\n\n\nclass HasVehicleAssetTransfer():\n pass\n\n\nclass Dealer(object):\n \"\"\"\n A network backed model\n Dealer model for the dealers table (pseudo)\n \"\"\"\n\n def __init__(self, id):\n self.id = id\n\n def __eq__(self, other):\n \"\"\"\n So you can do dealer_obj == another_dealer_obj\n \"\"\"\n return self.id == other.id\n\n @classmethod\n def find(cls, id):\n return cls(id)\n\n def __repr__(self):\n return '< Dealer id: {} >'.format(self.id)\n\n\nclass Org(db.Model, HasVehicleAssetTransfer):\n __tablename__ = \"org\"\n\n id = Column(Integer, primary_key=True, autoincrement=True)\n def __repr__(self):\n return '< Org id: {} >'.format(self.id)\n\n\nclass Records(BaseInitializer, db.Model):\n __tablename__ = \"records\"\n\n id = Column(Integer, primary_key=True, autoincrement=True)\n buyer_id = Column(String(50), nullable=False)\n buyer_type = Column(String(50), nullable=False)\n seller_id = Column(String(50), nullable=False)\n seller_type = Column(String(50), nullable=False)\n buyer__dealer = NetRelationship(prefix='buyer', _class=Dealer) # Network backed fields\n seller__dealer = NetRelationship(prefix='seller', _class=Dealer) # Network backed fields\n buyer = PolyField(prefix='buyer')\n seller = PolyField(prefix='seller')\n\n\nrelations = (\n Relation(data_class=Records, data_class_attr='buyer', ref_class_attr='buyer_records'),\n Relation(data_class=Records, data_class_attr='seller', ref_class_attr='seller_records')\n)\n\nHasVehicleAssetTransfer = create_polymorphic_base(relations=relations)\n```\n\nNow you can:\n\n```py\ndealer1 = Dealer(1)\ndealer2 = Dealer(2)\n\norg1 = Org(id=1)\norg2 = Org(id=2)\n\nrec1 = Records(buyer=org1, seller=dealer2, id=1)\nrec2 = Records(buyer=org1, seller=org2, id=2)\nrec3 = Records(buyer=dealer1, seller=dealer2, id=3)\nrec4 = Records(buyer=dealer1, seller=org1, id=4)\n\nassert rec1.buyer_type == 'org'\nassert rec1.buyer_id == 1\nassert rec1.buyer is org1\nassert org1.buyer_records == [rec1, rec2]\nassert org1.seller_records == [rec4]\nassert rec3.buyer is dealer1\n```\n\n## Using Polymorphic extension with Single table inheritence\n\n```py\nclass SourceOfData(db.Model):\n __tablename__ = 'juices'\n\n id = Column(Integer, primary_key=True, autoincrement=True)\n filter_type = Column(String(16), nullable=False)\n\n ADS_FILTER_TYPE = 'ads'\n NEWS_FILTER_TYPE = 'news'\n\n __mapper_args__ = {\n 'polymorphic_on': filter_type\n }\n\n\nclass AdsData(SourceOfData, HasVehicle):\n __mapper_args__ = {\n 'polymorphic_identity': SourceOfData.ADS_FILTER_TYPE\n }\n\n\nclass NewsData(SourceOfData, HasVehicle):\n __mapper_args__ = {\n 'polymorphic_identity': SourceOfData.NEWS_FILTER_TYPE\n }\n```\n\nNote that `HasVehicle` is subclassed by the `AdsData` and `NewsData` but NOT by their superclass, `SourceOfData`.\n\n\n\n# Coming from older generate_polymorphic_listener_function\n\nYou can simply import:\n\n`from polymorphic_sqlalchemy import generate_polymorphic_listener_function`\n\nand use it as before.\n\n\n# Running tests\n\n`pip install requirements-dev.txt`\n\n`pytest tests/`\n\n\n# Known Limitations and Bugs\n\n1. It is up to your implementation of the actual network backed model to provide the backref of the relationship. For example in the above example, there is `org1.buyer_records` automatically made for you since `org1` is a SQLAlchemy object. However `dealer1.buyer_records` is not automatically made for you,\n2. The values of the actual SQLAlchemy fields that get saved into the database are currently only set when you initially assign the object to the field. If you later modify the object from reference table, the value of the field in the data table does not update automatically. The opposite of this is true too:\n\n ```py\n dealer2 = Dealer(2)\n\n org1 = Org(id=1)\n org2 = Org(id=2)\n\n rec1 = Records(buyer=org1, seller=dealer2, id=1)\n rec2 = Records(buyer=org1, seller=org2, id=2)\n\n rec1.buyer_id = 2\n # NOTE: This is a bug. A solution might be to use SQLAlchemy events to update the object.\n # The problem is that setting the object will again update the fields which causes an infinite loop.\n assert rec1.buyer is org1\n\n # The opposite of it is true too and is a bug. If the reference object gets modified and it is already set in the data model,\n # the values do not update.\n org1.id = 20\n assert rec2.buyer is org1\n assert rec2.buyer_id != org1.id\n ```\n\n\n# Future developments\n\nThere is no plan to implement the following yet but in future we can combine the `[prefix]_id`, `[prefix]_type`, PolyField and NetRelationship all into one PolyField. And via Metaclass that Polyfield can dynamically generate all these other fields.\n\n\n", "description_content_type": null, "docs_url": null, "download_url": "https://github.com/wearefair/polymorphic_sqlalchemy/tarball/master", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/wearefair/polymorphic_sqlalchemy", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "polymorphic-sqlalchemy", "package_url": "https://pypi.org/project/polymorphic-sqlalchemy/", "platform": "", "project_url": "https://pypi.org/project/polymorphic-sqlalchemy/", "project_urls": { "Download": "https://github.com/wearefair/polymorphic_sqlalchemy/tarball/master", "Homepage": "https://github.com/wearefair/polymorphic_sqlalchemy" }, "release_url": "https://pypi.org/project/polymorphic-sqlalchemy/0.2.6/", "requires_dist": [ "SQLAlchemy", "inflection" ], "requires_python": "", "summary": "Polymorphic relations for SQLAlchemy", "version": "0.2.6" }, "last_serial": 3618972, "releases": { "0.2.6": [ { "comment_text": "", "digests": { "md5": "0bf83776277244ea9c93846c23ba7d34", "sha256": "298d5ada6236560ac9cee299f6e5ba893ad58f5832eb7f59ef25ee53f2197dfe" }, "downloads": -1, "filename": "polymorphic_sqlalchemy-0.2.6-py3-none-any.whl", "has_sig": false, "md5_digest": "0bf83776277244ea9c93846c23ba7d34", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 12576, "upload_time": "2018-02-26T22:34:23", "url": "https://files.pythonhosted.org/packages/11/9e/ed29b21a6517090c165d20950817c915ac4afcf6ca1aca5998c31e60acba/polymorphic_sqlalchemy-0.2.6-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "03e68e84351a41cf27215e8ddc250409", "sha256": "e0d39de89af35f9ae0ec89e089142b5d4d5bfd5ca75c526af2d3186308ca774b" }, "downloads": -1, "filename": "polymorphic_sqlalchemy-0.2.6.tar.gz", "has_sig": false, "md5_digest": "03e68e84351a41cf27215e8ddc250409", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10397, "upload_time": "2018-02-26T22:34:24", "url": "https://files.pythonhosted.org/packages/ef/da/727dd187574b516615bf3f6490f807f47a049503c89910ae734a4d0f733d/polymorphic_sqlalchemy-0.2.6.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "0bf83776277244ea9c93846c23ba7d34", "sha256": "298d5ada6236560ac9cee299f6e5ba893ad58f5832eb7f59ef25ee53f2197dfe" }, "downloads": -1, "filename": "polymorphic_sqlalchemy-0.2.6-py3-none-any.whl", "has_sig": false, "md5_digest": "0bf83776277244ea9c93846c23ba7d34", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 12576, "upload_time": "2018-02-26T22:34:23", "url": "https://files.pythonhosted.org/packages/11/9e/ed29b21a6517090c165d20950817c915ac4afcf6ca1aca5998c31e60acba/polymorphic_sqlalchemy-0.2.6-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "03e68e84351a41cf27215e8ddc250409", "sha256": "e0d39de89af35f9ae0ec89e089142b5d4d5bfd5ca75c526af2d3186308ca774b" }, "downloads": -1, "filename": "polymorphic_sqlalchemy-0.2.6.tar.gz", "has_sig": false, "md5_digest": "03e68e84351a41cf27215e8ddc250409", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10397, "upload_time": "2018-02-26T22:34:24", "url": "https://files.pythonhosted.org/packages/ef/da/727dd187574b516615bf3f6490f807f47a049503c89910ae734a4d0f733d/polymorphic_sqlalchemy-0.2.6.tar.gz" } ] }