{"id":15348408,"url":"https://github.com/stain/forgetsql","last_synced_at":"2026-01-27T13:34:08.049Z","repository":{"id":57431895,"uuid":"47025815","full_name":"stain/forgetSQL","owner":"stain","description":"Python ORM wrapper for SQL databases (no longer maintained)","archived":false,"fork":false,"pushed_at":"2015-11-28T15:36:07.000Z","size":92,"stargazers_count":2,"open_issues_count":3,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-14T11:46:02.899Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-2.1","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stain.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES","contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-11-28T14:09:32.000Z","updated_at":"2021-09-19T04:50:05.000Z","dependencies_parsed_at":"2022-08-30T12:31:36.414Z","dependency_job_id":null,"html_url":"https://github.com/stain/forgetSQL","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stain%2FforgetSQL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stain%2FforgetSQL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stain%2FforgetSQL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stain%2FforgetSQL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stain","download_url":"https://codeload.github.com/stain/forgetSQL/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247492463,"owners_count":20947541,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T11:47:51.512Z","updated_at":"2026-01-27T13:34:08.012Z","avatar_url":"https://github.com/stain.png","language":"Python","readme":"# forgetSQL\n\n* Author: Stian Soiland-Reyes \u003cstian@soiland-reyes.com\u003e\n* Homepage: https://github.com/stain/forgetSQL\n* License: [GNU Lesser General Public License (LGPL) 2.1](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)\n   or later. See the file [COPYING](COPYING) for details.\n* Status: **not maintained** _This repository is provided for archival purposes_\n\nforgetSQL is a Python module for accessing SQL databases by creating\nclasses that maps SQL tables to objects, normally one class pr. SQL\ntable. The idea is to forget everything about SQL and just worrying\nabout normal classes and objects.\n\n# Download\n\nSee the [GitHub releases](https://github.com/stain/forgetSQL/releases)\nfor the latest downloads, or check out from the\nGitHub project [stain/forgetSQL](https://github.com/stain/forgetSQL).\n\n# Installation\n\nInstallation of the forgetSQL module is pretty straight\nforward:\n\n    python setup.py install\n\nThis will install `forgetSQL.py` into `site-packages/` of your\nPython distribution.\n\n\n## Dependencies\n\n\n* Python 2.2.1 or newer\n* Some database module (tested: `MySQLdb`, `psycopg`)\n\nIf using `psycopg`, then `mx.DateTime` is needed to avoid a psycopg\nbug related to re-inserting dates. `psycopg` depends on `mx.DateTime`, so\nthat shouldn't normally be a problem.\n\n\n# What is forgetSQL?\n\n## Why forgetSQL?\n\nLet's start by showing an example using an imaginary database\n`mydatabase`:\n\nThis example is based on these SQL tables:\n\n**account**\n\naccountid   | fullname          | groupid\n----------- | ----------------- | --------\nstain       | Stian Soiland     | 15\nmagnun      | Magnus Nordseth   | 15\nstornes     | Sverre Stornes    | 17\nmjaavatt    | Erlend Mjaavatten | 15\n\n\n**group**\n\ngroupid | name\n------- | -----\n15      | unix\n17      | tie\n\n\nAnd your Python script should output something like:\n\n    Account details for Stian Soiland\n    Group unix (15)\n    Other members:\n    Magnus Nordseth\n    Erlend Mjaavatten\n\n\nIn regular SQL programming, this could be done something like this:\n\n```python\ncursor = dbconn.cursor()\ncursor.execute(\"SELECT fullname,groupid FROM account WHERE accountid=%s\",\n               ('stain',))\nfullname,groupid = cursor.fetchone()\nprint \"Account details for\", fullname\n\ncursor.execute(\"SELECT name FROM group WHERE groupid=%s\" % groupid)\n(groupname,) = cursor.fetchone()\nprint \"Group %s (%s)\" % (groupid, name)\n\ncursor.execute(\"\"\"SELECT fullname\n                  FROM account JOIN group USING (groupid)\n                  WHERE group.groupid=%s AND\n                        NOT account.accountid=%s\"\"\",\n               (groupid, accountid))\nprint \"Other members:\"\nfor (membername,) in cursor.fetchall():\n    print membername\n```\n\nNow, using forgetSQL:\n\n```python\n    from mydatabase import *\n    account = Account(\"stain\") # primary key\n    print \"Account details for\", account.fullname\n    group = account.group\n    print \"Group %s (%s)\" % (group.name, group.groupid)\n    print \"Other members: \"\n\n    for member in group.getChildren(Account):\n        # find Account with group as foreign key\n        if member \u003c\u003e account:\n            print member.fullname\n```\n\nNotice the difference in size and complexity of these two examples.\n\nThe first example is tightly bound against SQL. The programmer is forced\nto think about SQL instead of the real code. This programming style\ntends to move high-level details to SQL, even if it is not neccessary.\nIn this example, when getting \"other members\", the detail of skipping\nthe active user is done in SQL.\n\nThis would hardly save any CPU time on modern computers, but has made\nthe code more complex.  Thinking in SQL makes your program very large,\nas everything can be solved by some specialized SQL. Trying to change\nyour program or database structure at a later time would be a nightmare.\n\nNow, forgetSQL removes all those details for the every-day-SQL tasks. It\nwill not be hyper-effective or give you points in the\nlargest-join-ever-possible-contest, but it will help you focus on what\nyou should be thinking of, making your program work.\n\nIf you at a later point (when everything runs without failures)\ndiscovers that you need to optimize something with a mega-query in SQL,\nyou could just replace that code with regular SQL operations.\nOf course, if you've been using test-driven development\nyour tests will show if the replaced code works.\n\nAnother alternative could be to use _views_ and _stored procedures_, and\nlayer forgetSQL on top of those views and procedures. This has never\nbeen tested, though. =)\n\n## What does forgetSQL do?\n\nFor each table in your database, a class is created. Each instance\ncreated of these classes refer to a row in the given table. Each\ninstance have attributes that refer to the fields in the database.\nNote that the instance is not created until you access that particular\nrow.\n\nSo accessing a column of a row is simply accessing the attribute\n`row.column`.  Now, if this column is a reference to another table, a\nforeign key, instead of an identifier you will in `row.column` find an\ninstance from the other table, ie. from the other class.\n\nThis is what happens in the example above, `group = account.group`\nretrieves this instance. Further attribute access within this instance\nis resolved from the matching row in the group table.\n\nIf you want to change some value, you could just change the attribute\nvalue. In the example, if you want to change my name, simply run\n`account.fullname = \"Knut Carlsen\"`\n\nYou can retrieve every row in some table that refers to the current\nobject. This is what happens in `group.getChildren(Account)`, which will\nreturn a list of those Accounts that have a foreign key refering to\n`group`.\n\nIf you retrieve the objects several times, the constructor will return\nthe same object the second time (unless some timeout has expired). This\nmeans that changes done to the object is immediately visible to all\ninstances. This is to reflect normal behaviour in object oriented\nprogramming. Example:\n\n    \u003e\u003e\u003e stain = Account(\"stain\")\n    \u003e\u003e\u003e stain2 = Account(\"stain\")\n    \u003e\u003e\u003e stain.fullname = \"Stian Soiland-Reyes\"\n    \u003e\u003e\u003e print stain2.fullname\n    Stian Soiland-Reyes\n\n\n## What does forgetSQL not do?\n\nforgetSQL is not a way to store objects in a database. It is a way to\nuse databases as objects. You cannot store arbitrary objects in the\ndatabase unless you use pickling.\n\nforgetSQL does not help you with database design, although you might\nchoose a development style that uses regular classes and objects at\nfirst, and then design the database afterwards. You could then change\nyour classes to use forgetSQL for data retrieval and storage, and later\npossibly replace forgetSQL classes with even more advanced objects.\n\nforgetSQL does not remove the need of heavy duty SQL. In some\nsituations, SQL is simply the best solution. forgetSQL might involve\nmany SQL operations for something that could be done in a single\noperations with a large magic query. If something does not scale up\nwith forgetSQL, even if you refactored your code, you might try using\nSQL instead.  This example would use excessive time in a table with a\nmillion rows:\n\n\n```python\nfor row in table.getAll():\n    row.backedUp = True\n    row.save()\n```\n\nThis would involve creating one million object instances (each row), one\nmillion SELECTs (to get the other values that needs to be saved), and\none million UPDATEs.  By using `getAllIterator` you could reduce this to\njust one million UPDATEs (one SELECT, reusing the same object), but\nstill it would be far much slower than `UPDATE table SET\nbackedUp=true`.\n\nforgetSQL does not support commits/rollback. This might be implemented\nlater, but I'm still unsure of how to actually use this in programming.\nAny suggestions are welcome.\n\n### Keeping in sync\n\nforgetSQL does not ensure that objects in memory are in sync with what\nis stored in the database. The values in the object will be a snapshot\nof how the row were at the time you first tried to retrieve an\nattribute. If you change some value, and then save the object, the row\nis updated to your version, no matter what has happened in the database\nmeanwhile. An object does not timeout while in memory, it does not\nrefresh it's values unless you call `_loadDB()` manually, as\nautomatically updating could confuse programmers. However, a timeout\nvalue is set, and if exceeded, *new* objects retrieved from database\n(ie. `Account(\"stain\")` will be fresh.\n\nIt is not easy to make a general way to ensure objects are updated. For\ninstance, always checking it could be heavy. It could also confuse some\nprograms if an object suddenly changes some of it's attributes without\ntelling, this could fuck up any updates the program is attempting to do.\nOn the other hand, saving a changed object as forgetSQL is now, will\noverwrite *all* attributes, not just the changed ones.\n\n\n# Usage\n\n## forgetsql-generate\n\nBefore you can use forgetSQL, you will need to generate a module\ncontaing the classes representing database tables. Luckily, forgetSQL\nships with a program that can do this for you by guessing.\n\nThe program is called `forgetsql-generate` and should be installed by\n`setup.py` or the packaging system. You might need the devel-version\nof the forgetSQL package.\n\nCreate a file `tables.txt`, with a list of database tables, one per\nline. (This is needed since there is no consistent way to query a\ndatabase about it's tables)\n\nThen generate the module representing your tables:\n\n    forgetsql-generate --dbmodule psycopg --username=johndoe\n                         --password=Jens1PuLe --database=genious\n                         --tables tables.txt --output Genious.py\n\nAlternative, you could pipe the table list to `forgetsql-generate`\nand avoid `--tables` -- and likewise drop `--output` and capture stdout\nfrom forgetsql-generate.\n\nThe generated module is ready for use, except that you need to\nset database connecting details. One possible way is included in the\ngenerated code, commented out and without a password.\n\nIt is recommended to set connection details from the outside instead,\nsince the tables might be used by different parts of a system using\ndifferent database passwords, connection details could be in a\nconfiguration file, you need persistent database connections, etc.\n\nThe way to do this is to set `Genious._Wrapper.cursor` to a cursor\nmethod, and `Genious._Wrapper._dbModule` to the database module used:\n\n```Python\nimport Genious\nimport psycopg\nconn = psycopg.connect(user=\"blal\", pass=\"sdlksdlk\", database=\"blabla\")\nGenious._Wrapper.cursor = conn.cursor()\nGenious._Wrapper._dbModule = psycopg\n```\n\n\n## Normal use\n\nWe'll call a class that is a representation of a database table a\nforgetter, because it inherits `forgetSQL.Forgetter`.\nThis chapter will present normal usage of such forgetters by examples.\n\n### Getting a row by giving primary key\n\nExample:\n\n```python\naccount = Account(\"stain\")\nprint account.fullname\n```\n\nIf the primary key is wrong (ie. the row does not exist) accessing\n`account.fullname` will raise `forgetSQL.NotFound`. The object is\nactually not loaded from the database until a attribute is read.\n(delayed loading) One problem with that is that `forgetSQL.NotFound`\nwill not be raised until the attribute is read.\n\nTo test if the primary key is valid, force a load:\n\n```python\naccount = Account(\"stain\")\ntry:\n    account.load()\nexcept forgetSQL.NotFound():\n    print \"Cannot find stain\"\n    return\n```\n\n### Getting all rows in a table\n\nExample:\n\n```python\nallAccounts = Account.getAll()\nfor account in allAccounts:\n    print account.accountid, account.fullname\n```\n\nNote that `getAll` is a class method, so it is available even before\ncreating some `Account`. The returned list will be empty if nothing is\nfound.\n\nAlso note that if what you want to do is to iterate, using\n`getAllIterator()` would work well. This avoids creating all objects\nat once.\n\n### To create a new row in a table\n\nExample:\n\n```python\naccount = Account()\naccount.accountid = \"jennyme\" # primary key\naccount.fullname = \"Jenny Marie Ellingsaeter\"\naccount.save()\n```\n\nIf you have forgotten to set some required fields, save() will fail. If\nyou don't set the primary key, forgetSQL will try to guess the sequence\nname (`tablename_primarykey_seq`) to retrieve a new one. This might or\nmight not work. For MySQL some other magic is involved, but it should\nwork.\n\n\n### Change some attribute\n\nExample:\n\n```python\naccount = Account(\"stain\")\naccount.fullname = \"Stian Stornes\" # got married to a beautiful man\n```\n\nYou can choose wether you want to call `save()` or not. If you don't call\n`save()`, the object will be saved when the object reference disappaers\n(ie. del account, end of function, etc.) and collected by the garbage\ncollector. Note that this might be delayed, and that any errors\nwill be disgarded.\n\nIf you are unsure if you have used the correct data type or want to\ncatch save-errors, use `save()`:\n\n```python\ngroup = Group(1)\ngroup.accountid = 'itil' # a string won't work in a integer field\ntry:\n    group.save()\nexcept Exception, e:\n    print \"Could not save group %s: %s\" % (group, e)\n```\n\nThe exception raised will be database module specific, like\n`psycopg.ProgrammingError`, possible containing some useful information.\n\n`save()` will return `True` if successful.\n\n### Undoing an attribute change\n\nIf you changed an attribute, and you don't want to save the change to\nthe database (as this will happen when the garbage collector kicks in),\nyou have two choices:\n\nReset the instance to a blank state:\n\n  ```python\n       group.reset()\n  ```\n\nThis sets everything to `None`, including the primary key.\nIf you have referenced the instance anywhere else, they\nwill now experience a blank instance.\n\nOr reload from database:\n\n```python\ngroup.load()\n```\nNote, `load()` will perform a new SELECT.  \n\nNote that you don't have to `reset()` if you haven't changed any\nattributes, the instance will only save if anything has changed.\n\n\n### Accessing foreign keys\n\nExample:\n\n```python\naccount = Account(\"stain\")\nprint account.group.accountid\nprint account.group.name\n```\n\nAn attribute which is a foreign key to some other table will be\nidentified by forgetsql-generate if it's name is something like\n`other_table_id`. If the generator could not identify foreign keys\ncorrectly, modify `_userClasses` in the generated  Forgetter\ndefinition. (See _Specializing the forgetters_).\n\nTo access the real primary key, use account.group.accountid or\n`account.group._getID()`. Note that the latter will return a tupple\n(in case the primary key contained several columns).o\n\nYou can set a foreign key attribute to a new object from the\nforeign class:\n\n```python\nimport random\nallGroups = Group.getAll()\nfor account in Account.getAll():\n    # Set the group to one of the Group instances\n    # in allGroups\n    account.group = random.choice(allGroups)\ndel account\n# Note that by reusing the account variable all of these\n# will be saved by the garbage collector\n```\n\nor to just the foreign primary key:\n\n```python\naccount.group = 18\n```\n\nNote that this referencing magic makes JOIN unneccessary in many cases,\nbut be aware that due to lazy loading (attributes are not loaded from\ndatabase before they are accessed for the first time), in some cases\nthis might result in many SELECT-calls. There are ways to avoid this,\nsee _Wrapping SQL queries_.\n\n\n### Finding foreign keys\n\nYou might want to walk in reverse, finding all accounts that have a\ngiven group as a foreign key:\n\n```python\ngroup = Group(15)\nmembers = group.getChildren(Account)\n```\n\nThis is equivalent to SQL:\n\n```sql\nSELECT * FROM account WHERE groupid=15\n```\n\n### Deleting an instance\n\nNote that although rows are represented as instances, they will not be\ndeleted from the database by dereferencing. Simply removing a name\nbinding only removes the representation. (and actually forces a\n`save()` if anything has changed).\n\nTo remove a row from the database:\n\n```python\naccount = Account(\"stornes\")\naccount.delete()\n```\n\n`delete()` might fail if your database claims reference integrity but\ndoes not cascade delete:\n\n```python\ngroup = Group(17)\ngroup.delete()\n```\n\n## Advanced use\n\n### WHERE-clasules\n\nYou may specify a where-sentence to be inserted into the SELECT-call of\n`getAll`-methods:\n\n```python\nmembers = Account.getAll(where=\"groupid=17\")\n```\n\nNote that you must take care of proper escaping on your own by using\nthis approach. Most database modules have some form of escape functions.\n\nIn many cases, what you want to do with WHERE is probably the\nsame as with `getChildren()`:\n\n```python\ngroup = Group(17)\nmembers = group.getChildren(Account)\n```\n\nThis will be as effective as generating a WHERE-clasule, since\n`group.load()` won't be run (no attributes accessed, only the primary\nkey).\n\nThe sentence is directly inserted, so you need to use the actual SQL\ncolumn names, not the attribute names. You can use AND and OR as you\nlike.\n\nIf you have several clauses to be AND-ed together, forgetSQL can do this\nfor you, as the where-parameter can be a list:\n\n```python\nwhere = []\nwhere.append(\"groupid=17\")\nif something:\n    where.append(\"fullname like 'Stian%'\")\nAccount.getAll(where=where)\n```\n\n### Sorting\n\nIf you have specified `_orderBy` (see _Specializing the forgetters_),\nthe results of `getAll*` and `getChildren` will be ordered by those\nattributes.\n\nIf you want to specify ordering manually, you can supply a keyword\nargument to `getAll``:\n\n```python\nall = Account.getAll(orderBy=\"fullname\")\n```\n\nThe value of `orderBy` could be either a string (representing the\nobject attribute to be sorted) or a tupple of strings (order by A, then\nB, etc.). Note that you can only order by attributes defined in the\ngiven table.\n\nIf you want some other fancy sorting, sort the list after retrieval\nusing regular `list.sort()`:\n\n```python\nall = Account.getAll()\nall.sort(lambda a,b:\n            cmp(a.split()[-1],\n                b.split()[-1]))\n# order by last name! :=)\n```\n\n\n### More getAll\n\nThere are specialized `getAll` methods for different situations.\n\nIf you just want the IDs in a table:\n\n```python\n\u003e\u003e\u003e all = Account.getAllIDs()\n['stornes', 'stain', 'magnun', 'mjaavatt']\n```\n\nThe regular `getAll()` actually runs `getAllIDs()`, and returns a\nlist of instances based on those IDs. The real data is not loaded\nuntil attribute access. In some cases, this might be OK, for instance if\nyou want to call getChildren and really don't care about the attribute\nvalues.\n\nIf you are going to iterate through the list, a common case, use\ninstead:\n\n```python\nfor account in Account.getAllIterator():\n    print account.fullname\n```\n\nThis will return an iterator, not a list, returning `Account` objects.\nFor each iteration, a new instance is returned, with all fields\nloaded. Internally in the iterator, a buffer of results from SELECT * is\ncontained.\n\nIn Python, object creation is a bit expensive, so you might reuse the\nsame object for each iteration by creating it first and specifying it\nas the keyword argument `useObject`:\n\n```python\nfor account in Account.getAllIterator(useObject=Account()):\n    print account.fullname\n```\n\nNote that changes made to account in this case will be flushed unless\nyou manually call `save()`. Do not pass this instance on, as it's content\nwill change for each iteration.\n\nFinally, `getAllText()` will use `_shortView` (See _Specializing\nthe forgetters_) and return tuples of (id, text). This is useful for\na dropdown-list of selectors.\n\n\n# Specializing the forgetters\n\nBy specifying the `Forgetter` subclasses manually, or correcting\nthe autogenerated ones from `forgetsql-generate`, you can fix any\nmistakes in `_sqlFields`, etc.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstain%2Fforgetsql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstain%2Fforgetsql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstain%2Fforgetsql/lists"}