{ "info": { "author": "Maximiliano Pin", "author_email": "mxcpin@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Other Environment", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", "Programming Language :: Python" ], "description": "========\nRedmodel\n========\n\nPython Data Models for Redis.\nMaximiliano Pin \n\n\nDescription\n-----------\n\nRedmodel is born as an alternative to Redisco. It's focused on model\nrelationships and indexes, and tries to be a thin but powerful layer on top of\nredis.\n\nThe concepts of Handle and Writer are introduced, which make redmodel less\nmagic than redisco, but allow a finer control on what you are doing:\n\n- A handle is a reference to an object stored in redis. A query, for instance,\n returns a handle or a set of handles, and does not load automatically the\n objects from redis. You must load them explicitly.\n\n- A writer is an object used to write model objects or containers. The main\n reason for this is to allow writing code which only reads some models data,\n but is not able to write them. That's good to divide responsibilities in\n different modules, threads, or programs.\n\nTest-driven development was used to create Redmodel, so it is and will be\nextensively tested by automated tests located in the test directory.\nI recomend you read the testing code if you want to understand the internals\nof Redmodel.\n\n\nInstallation\n------------\n\nRedmodel requires redis-py. Install it first. Then run:\n\n sudo python ./setup.py install\n\n\nQuick Example\n-------------\n\nThis example code shows some Redmodel features. It's located in\ntest/quick_example.py (warning: test programs flush your redis database!).\n\n::\n\n from redmodel.models import Model, Attribute, IntegerField, ListField\n from redmodel.models import ModelWriter, ListFieldWriter\n from redmodel.containers import List\n\n # clean all data\n from redmodel import connection\n connection.flushdb()\n\n # define Color class\n class Color(Model):\n name = Attribute(unique = True)\n rgb = IntegerField()\n\n # define Person class\n class Person(Model):\n name = Attribute(unique = True)\n fave_colors = ListField(Color, indexed = True)\n\n # create writer objects\n color_writer = ModelWriter(Color)\n person_writer = ModelWriter(Person)\n fave_colors_writer = ListFieldWriter(Person.fave_colors)\n\n # create some colors\n c1 = Color(name = 'red', rgb = 0xff0000)\n c2 = Color(name = 'green', rgb = 0x00ff00)\n c3 = Color(name = 'blue', rgb = 0x0000ff)\n\n # save the colors in Redis\n map(color_writer.create, [c1, c2, c3])\n\n # create a person in Redis\n person = Person(name = 'Cristina')\n person_writer.create(person)\n\n # set fave colors of this person\n fave_colors_writer.append(person.fave_colors, Color.find(name = 'blue'))\n fave_colors_writer.append(person.fave_colors, Color.find(name = 'green'))\n\n # list people who like color green\n green = Color.find(name = 'green')\n for handle in Person.multifind(fave_colors__contains = green):\n person = Person(handle)\n print(person.name)\n # print all colors she likes\n for color_handle in List(person.fave_colors):\n print(str(Color(color_handle)))\n\n\nModel Definition\n----------------\n\nThese example models have been created to test all redmodel features. They\nmodel the data of an hypothetic game of gangs, whose members are fighters\nwhich move along different cities. A game extension adds a skill system to\nthe fighters.\n\n::\n\n from redmodel.models import Model, Attribute, BooleanField, IntegerField, FloatField, UTCDateTimeField\n from redmodel.models import ReferenceField, ListField, SetField, SortedSetField, Recursive\n\n # City with a name, a boolean, and a list of connections to other cities\n # (recursive references).\n class City(Model):\n name = Attribute()\n coast = BooleanField()\n connections = ListField(Recursive)\n\n # Weapon with a description and its power value.\n class Weapon(Model):\n description = Attribute()\n power = FloatField()\n\n # Fighter with name, age, weight, join time, and current city.\n # - The name is defined as unique, so fighters are indexed by name (we can\n # find a fighter by name), and it cannot be repeated. The index is a\n # redis hash.\n # - The datetime field is stored as an integer (no microseconds). It may be\n # better to use an IntegerField directly, in order to avoid conversions.\n # - The current city is indexed, so we can find which fighters are in a\n # city. This index is a collection of redis sets.\n # - Attributes which are zindexed have a redis sorted set associated, so we\n # can execute queries like Fighter.zfind(age__lt = 30).\n # - Weapons are sorted by power. In this case, we have a redis sorted set\n # for every Fighter object (with zindexed, we have one global sorted set).\n # - The weapons field could have been created as:\n # weapons = SortedSetField(Weapon)\n # So entries are not sorted by a specific field, but a 'score' must be\n # specified as an additional parameter.\n # If a field is specified, then owned must be True (see below for an\n # explanation about 'owned'), and a weapon's power should not be updated\n # directly, but using SortedSetFieldWriter's update or update_all methods,\n # so the sorted set is automatically and atomically updated.\n class Fighter(Model):\n name = Attribute(unique = True)\n age = IntegerField(zindexed = True)\n weight = FloatField(zindexed = True)\n joined = UTCDateTimeField(zindexed = True)\n city = ReferenceField(City, indexed = True)\n weapons = SortedSetField(Weapon, Weapon.power, owned = True)\n\n # Gang with a name and a set of member fighters.\n # - A fighter can only be the leader of one gang. This index is a redis\n # hash.\n # - Members are indexed uniquely. That means a fighter can be in one gang\n # only. This index is a single redis hash.\n # - Cities where the gang operates are indexed, so we can find which gangs\n # operate in a city. This index is a collection of redis sets.\n # - The headquarter city (hqcity) is listed: This means that redis lists\n # exist containing gangs with their headquarters in each city. This is\n # similar to indexed, but a list is used instead of a set. This is great\n # to keep the elements sorted by creation time, but it's bad for removing\n # or searching.\n class Gang(Model):\n name = Attribute()\n leader = ReferenceField(Fighter, unique = True)\n members = SetField(Fighter, unique = True)\n cities = SetField(City, indexed = True)\n hqcity = ReferenceField(City, listed = True)\n\n # Skill that fighters can have.\n class Skill(Model):\n category = Attribute()\n name = Attribute()\n description = Attribute()\n\n # Skill instance: a skill with a value.\n class SkillInstance(Model):\n skill = ReferenceField(Skill)\n value = IntegerField()\n\n # Skills a fighter has.\n # - This model is owned by the Fighter model (\"owner = Fighter\"). So, this\n # model is an extension to the Fighter model. This is useful to implement\n # plugins or independent modules with independent data, instead of\n # modifying the base model (Fighter in this example).\n # - SkillInstance objects in the skills list are owned by this model\n # (\"owned = True\"). This means that:\n # 1. New SkillInstance objects can be created and added to the list\n # atomically.\n # 2. An object removed from the list is deleted automatically.\n class FighterSkillList(Model):\n owner = Fighter\n skills = ListField(SkillInstance, owned = True)\n\n\nCreating Objects\n----------------\n\nLet's create some data for our example model.\n\nCreate some cities:\n\n::\n\n from redmodel.models import ModelWriter\n city_writer = ModelWriter(City)\n c1 = City(name = 'Reixte', coast = True)\n c2 = City(name = 'Damtoo', coast = True)\n c3 = City(name = 'Toynbe', coast = False)\n map(city_writer.create, [c1, c2, c3])\n\nCreate connections between cities:\n\n::\n\n from redmodel.models import ListFieldWriter\n city_connections_writer = ListFieldWriter(City.connections)\n city_connections_writer.append(c1.connections, c2)\n city_connections_writer.append(c2.connections, c1)\n city_connections_writer.append(c1.connections, c3)\n city_connections_writer.append(c3.connections, c1)\n\nCreate some fighters:\n\n::\n\n from datetime import datetime\n fighter_writer = ModelWriter(Fighter)\n dtime = datetime.utcfromtimestamp(1400000000)\n f1 = Fighter(name = 'Alice', age = 29, weight = 73.2, joined = dtime, city = City.by_id(1))\n f2 = Fighter(name = 'Bob', age = 32, weight = 98, joined = dtime, city = City.by_id(1))\n map(fighter_writer.create, [f1, f2])\n\nCreate a gang and add both fighters to it:\n\n::\n\n gang_writer = ModelWriter(Gang)\n g = Gang(name = 'Ghetto Warriors', leader = f1)\n gang_writer.create(g)\n\n from redmodel.models import SetFieldWriter\n gang_members_writer = SetFieldWriter(Gang.members)\n gang_members_writer.append(g.members, f1)\n gang_members_writer.append(g.members, f2)\n\nAdd some weapons to fighter f1. Notice that we attach weapon_writer to\nfighter_weapons_writer as the \"element_writer\", so objects are created and\ndeleted automatically (we can do this because the \"weapons\" container of\nFighter has \"owned = True\"). Furthermore, this will be useful when updating\nthe objects to keep the set sorted (see later):\n\n::\n\n weapon_writer = ModelWriter(Weapon)\n fighter_weapons_writer = SortedSetFieldWriter(Fighter.weapons, weapon_writer)\n w1 = Weapon(description = 'second', power = 50.5)\n w2 = Weapon(description = 'third', power = 34.2)\n w3 = Weapon(description = 'first', power = 50.7)\n for w in w1, w2, w3:\n fighter_weapons_writer.append(f1.weapons, w)\n\nCreate some skill definitions:\n\n::\n\n skill_writer = ModelWriter(Skill)\n sk1 = Skill(category = 1, name = 'Strength', description = 'Strength...')\n sk2 = Skill(category = 3, name = 'Karate', description = 'Karate...')\n map(skill_writer.create, [sk1, sk2])\n\nAttach FighterSkillList objects to existing Fighter objects:\n\n::\n\n fighter_skill_list_writer = ModelWriter(FighterSkillList)\n f1skills = FighterSkillList()\n f2skills = FighterSkillList()\n fighter_skill_list_writer.create(f1skills, f1)\n fighter_skill_list_writer.create(f2skills, f2)\n\nAdd skill instances to fighter skill lists. Notice that we attach\nskill_instance_writer to fighter_skills_writer as the \"element_writer\":\n\n::\n\n skill_instance_writer = ModelWriter(SkillInstance)\n fighter_skills_writer = ListFieldWriter(FighterSkillList.skills, element_writer = skill_instance_writer)\n\n ski1 = SkillInstance(skill = sk1, value = 21)\n ski2 = SkillInstance(skill = sk2, value = 15)\n fighter_skills_writer.append(f1skills.skills, ski1)\n fighter_skills_writer.append(f1skills.skills, ski2)\n\n ski1 = SkillInstance(skill = sk1, value = 27)\n ski2 = SkillInstance(skill = sk2, value = 91)\n fighter_skills_writer.append(f2skills.skills, ski1)\n fighter_skills_writer.append(f2skills.skills, ski2)\n\n\nReading Data\n------------\n\nWe can build a handle for an object by id. This implies no access to redis.\nIf the object does not exist, the handle is valid anyway:\n\n::\n\n handle = Gang.by_id(1)\n\nTo read the data from redis, we create a model object, passing a handle to the\nconstructor:\n\n::\n\n gang = Gang(handle)\n\nContainer fields (lists, sets and sorted sets) are not read automatically from\nredis. Instead, a handle for the container is generated in the owner object.\nThey are loaded using the List, Set and SortedSet classes from\nredmodel.containers. A List, Set or SortedSet object contains a collection\nof object handles (but notice that containers of elementary types can also\nexist).\n\nThis is how we list the gang member fighters:\n\n::\n\n from redmodel.containers import Set\n members = Set(gang.members)\n for handle in members:\n print(str(Fighter(handle)))\n\nSortedSet has some query methods in addition to the read constructor.\nThese methods wrap z* redis functions (plus the convenience zfind method).\nThese are further explained in the Containers section. So, we can make some\nqueries on a fighter weapon set:\n\n::\n\n fighter1 = Fighter(Fighter.by_id(1))\n # normal read constructor: returns sorted weapon handle list\n sorted_weapons = SortedSet(fighter1.weapons)\n # read constructor with filter (returns weapons with power greater than 50)\n powerful_weapons = SortedSet(fighter1.weapons, gt = 50)\n # alternative method to get the same\n powerful_weapons = SortedSet.zfind(fighter1.weapons, gt = 50)\n # top 10 fighter1's most powerful weapons\n top_weapons = SortedSet.zrevrange(fighter1.weapons, 0, 9)\n\nFor owned models, use by_owner() to create handles and read data:\n\n::\n\n # an owner handle or object can be used\n fighter1 = Fighter(Fighter.by_id(1))\n handle = FighterSkillList.by_owner(fighter1)\n fsl = FighterSkillList(handle)\n\n\nQueries\n-------\n\nFind in unique index:\n\n::\n\n hbob = Fighter.find(name = 'Bob')\n if not hbob:\n print('Fighter not found.')\n\n # trying to read from an invalid handle would raise NotFoundError,\n # so we can do this instead:\n from redmodel.models import NotFoundError\n try:\n fighter = Fighter(Fighter.find(name = 'Bob'))\n except NotFoundError:\n print('Fighter not found.')\n\n\nFind in non unique index:\n\n::\n\n # find all fighters which are currently in city number 1;\n # the result is a set of Fighter handles\n city_fighters = Fighter.multifind(city = City.by_id(1))\n\nFor fields which are 'listed' instead of 'indexed', use getlist:\n\n::\n\n # find all gangs with headquarters in a given city\n gangs_by_hqcity = Gang.getlist(hqcity = City.by_id(3))\n # get the first 10 elements only\n first_gangs_by_hqcity = Gang.getlist(0, 9, hqcity = City.by_id(3))\n\nFind in unique container index:\n\n::\n\n bobs_gang = Gang(Gang.find(members__contains = hbob))\n\nFind in non unique container index:\n\n::\n\n # find all gangs which operate in city number 3;\n # the result is a set of Gang handles\n city_gangs = Gang.multifind(cities__contains = City.by_id(3))\n\n\nQueries on Sorted Indexes\n-------------------------\n\nFor fields which are zindexed, methods that wrap z* redis functions are\navailable (similar to those on sorted set fields explained before).\nThese methods return a sorted list of handles:\n\n::\n\n # get a list of Fighter handles sorted by fighters weight\n # (notice there's no sorting operation here; we are keeping a sorted index)\n sorted_by_weight = Fighter.zrange('weight')\n\n # get the top ten heaviest fighters\n heaviest_fighters = Fighter.zrevrange('weight', 0, 9)\n\n # get list of fighters less or equal than 24 years old\n # (notice you can use zfind for this; see below)\n young_fighters = Fighter.zrangebyscore('age', '-inf', 24)\n\n # get first 3 fighters greater than 39 years old (39 not included)\n mature_fighters = Fighter.zrangebyscore('age', '(39', '+inf', 0, 3)\n\nThe convenience zfind method may be used instead of zrangebyscore:\n\n::\n\n young_fighters = Fighter.zfind(age__lt = 25)\n mature_fighters = Fighter.zfind(age__gte = 40)\n in_their_twenties = Fighter.zfind(age__in = (20, 29))\n age_match = Fighter.zfind(age = 23)\n joined_before_2020 = Fighter.zfind(joined__lt = datetime(2020, 1, 1))\n\nOther available methods:\n\n::\n\n # count fighters in an age range\n Fighter.zcount('age', 20, 23)\n\n # get position of fighter in zero-based weight ranking (increasing order)\n Fighter.zrank('weight', fighter1)\n\n # get position of fighter by handle in weight ranking (decreasing order)\n Fighter.zrevrank('weight', hfighter2)\n\n\nUpdating Data\n-------------\n\nObject attributes can be updated in two ways:\n(notice that indexes are updated automatically)\n\n::\n\n # Method 1:\n fighter = Fighter(Fighter.by_id(2))\n fighter_writer.update(fighter, name = 'Robert', weight = 99.9)\n\n # Method 2:\n fighter = Fighter(Fighter.by_id(2))\n fighter.name = 'Bobby'\n fighter.age = 41\n fighter_writer.update_all(fighter)\n\nUpdate a sorted set field owned element while resorting the set atomically:\n\n::\n\n w2 = Weapon(Weapon.by_id(2))\n fighter_weapons_writer.update(fighter1.weapons, w2,\n power = 70, description = 'improved')\n w2.power -= 60\n w2.description = 'degraded'\n fighter_weapons_writer.update_all(fighter1.weapons, w2)\n\nDelete an object. Notice that containers referencing this object will contain\nnow an invalid handle! Use container fields with \"owned = True\" whenever\npossible, so objects are deleted automatically when removing its handle from\nthe container.\n\n::\n\n fighter_writer.delete(fighter1)\n\nRemove items from containers (see note above about containers with owned\nelements):\n\n::\n\n gang1 = Gang(Gang.by_id(1))\n gang_members_writer.remove(gang1.members, Fighter.by_id(2))\n\n\nContainers\n----------\n\nWe've seen how to use container fields in models, but standalone containers may\nalso be used, which can hold model objects and even be indexed. Some examples:\n\n::\n\n from redmodel.containers import List, Set, SortedSet\n from redmodel.containers import ListHandle, SetHandle, SortedSetHandle\n from redmodel.containers import ListWriter, SetWriter, SortedSetWriter\n\n # a list of strings\n writer = ListWriter(str)\n hlist = ListHandle('mylist', str)\n writer.append(hlist, 'spam')\n writer.append(hlist, 'eggs')\n read_list = List(hlist)\n\n # a set of integers\n writer = SetWriter(int)\n hset = SetHandle('myset', int)\n writer.append(hset, 11)\n writer.append(hset, 13)\n writer.append(hset, 17)\n read_set = Set(hset)\n\n # a sorted set of strings (sorted by a float score)\n writer = SortedSetWriter(str)\n hzset = SortedSetHandle('myzset', str)\n writer.append(hzset, 'spam', 3.25)\n writer.append(hzset, 'eggs', 3.24)\n read_zset = SortedSet(hzset)\n read_zset = SortedSet(hzset, gt = 3.24) # returns ('spam',)\n\n # SortedSet has z* query methods which map redis z* functions,\n # plus convenience zfind method (as an alternative to zrangebyscore)\n sorted_elements = SortedSet.zrange(hzset)\n top_ten = SortedSet.zrevrange(hzset, 0, 9)\n first_greater = SortedSet.zrangebyscore(hzset, '(3.24', '+inf', 0, 1)\n all_greater = SortedSet.zfind(hzset, gt = 3.24)\n score_match = SortedSet.zfind(hzset, eq = 3.24)\n range_count = SortedSet.zcount(hzset, 3.24, 3.25)\n element_position = SortedSet.zrank(hzset, 'spam')\n rev_element_position = SortedSet.zrevrank(hzset, 'eggs')\n\n # a list of objects\n writer = ListWriter(Fighter)\n hlist = ListHandle('mylist', Fighter)\n writer.append(hlist, Fighter.by_id(2))\n\n # an indexed set\n writer = SetWriter(int, index_key = 'myindex')\n hset1 = SetHandle('myset:1', int)\n hset2 = SetHandle('myset:2', int)\n for i in 1, 2, 3:\n writer.append(hset1, i)\n for i in 2, 3, 4, 5:\n writer.append(hset2, i)\n # redis sets 'myindex:1' to 'myindex:5' have been created\n\n # an unique indexed set\n writer = SetWriter(int, index_key = 'myindex', unique_index = True)\n hset1 = SetHandle('myset:1', int)\n hset2 = SetHandle('myset:2', int)\n for i in 1, 2, 3:\n writer.append(hset1, i)\n writer.append(hset2, i + 3)\n # redis hash 'myindex' has been created with these values:\n # {'1': '1', '2': '1', '3': '1', '4': '2', '5': '2', '6': '2'}\n\n\nCredits\n-------\n\nThanks to Tim Medina, author of Redisco. Most concepts in Redmodel are taken\nfrom Redisco. Also, I learned a lot from his code.\n\nThanks to Salvatore Sanfilippo for creating Redis.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/beatmax/redmodel", "keywords": "Redis,model,container", "license": "GPL", "maintainer": null, "maintainer_email": null, "name": "redmodel", "package_url": "https://pypi.org/project/redmodel/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/redmodel/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://github.com/beatmax/redmodel" }, "release_url": "https://pypi.org/project/redmodel/0.3.1/", "requires_dist": null, "requires_python": null, "summary": "Python Library for Data Models Persisted in Redis", "version": "0.3.1" }, "last_serial": 798675, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "31501bfa18b8fcfa446242518865fec5", "sha256": "a8734d1ecccc708beecb48968d1e8f9d71652066e4e389dcfd6790d70b3666f5" }, "downloads": -1, "filename": "redmodel-0.1.0.tar.gz", "has_sig": false, "md5_digest": "31501bfa18b8fcfa446242518865fec5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30995, "upload_time": "2011-03-05T20:26:42", "url": "https://files.pythonhosted.org/packages/7f/28/d00b0187947c4de0f413a7bdd96720b1682a781ffd4704ed7c290a2f49c6/redmodel-0.1.0.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "c7819c59456047f9f0f08c3f0b2d2dc5", "sha256": "6eff3260c433520af635497b00505a790c4576aa774999edd3cac04902cf681b" }, "downloads": -1, "filename": "redmodel-0.2.0.tar.gz", "has_sig": false, "md5_digest": "c7819c59456047f9f0f08c3f0b2d2dc5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34267, "upload_time": "2011-08-13T23:35:37", "url": "https://files.pythonhosted.org/packages/15/fe/375185fe2c7d0d1d3ada3ddea57e8820d217fb5f0c9a376fa5699660acf0/redmodel-0.2.0.tar.gz" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "9faa2ca3e167703a24b3dc26a6654ed0", "sha256": "d9a9dc060848c5fae844652af2e5369246124f2b31ba6abfc133d42d24c0267b" }, "downloads": -1, "filename": "redmodel-0.3.0.tar.gz", "has_sig": false, "md5_digest": "9faa2ca3e167703a24b3dc26a6654ed0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38038, "upload_time": "2011-08-21T13:22:32", "url": "https://files.pythonhosted.org/packages/d9/14/3ff71f7d0f0a2a4f7aad5a2f9ebe2ed60fa2fdf2fe29e7e2f7280296d5e2/redmodel-0.3.0.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "b0b232b31c5b084709b57e5b16d8aeba", "sha256": "0e4b6259c7456e659ab5d0dfc44ca5f3a978a8af91540a8d421e9b81df461eb6" }, "downloads": -1, "filename": "redmodel-0.3.1.tar.gz", "has_sig": false, "md5_digest": "b0b232b31c5b084709b57e5b16d8aeba", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38888, "upload_time": "2011-08-25T12:26:45", "url": "https://files.pythonhosted.org/packages/be/35/65a36df472b58f2b85d67db553ebbb508a2e0b4a472afe570b064d748364/redmodel-0.3.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "b0b232b31c5b084709b57e5b16d8aeba", "sha256": "0e4b6259c7456e659ab5d0dfc44ca5f3a978a8af91540a8d421e9b81df461eb6" }, "downloads": -1, "filename": "redmodel-0.3.1.tar.gz", "has_sig": false, "md5_digest": "b0b232b31c5b084709b57e5b16d8aeba", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38888, "upload_time": "2011-08-25T12:26:45", "url": "https://files.pythonhosted.org/packages/be/35/65a36df472b58f2b85d67db553ebbb508a2e0b4a472afe570b064d748364/redmodel-0.3.1.tar.gz" } ] }