Package: pogodb
PogoDBSimple NoSQL wrapper around Postgres' JSONB type.InstallationPogoDB is installable via pip, following atwo-stepprocess:pip install pogodbpip install psycopg2ORpip install psycopg2-binarySince thepsycopg2/psycopg2-binarysplit, instead of forcing a dependency on either one, the choice is left to you. PogoDB should work with either.Tip:Ifpip install psycopg2fails, trypip install psycopg2-binary.QuickstartTo connect from a Python Shell, usepogodb.shellConnect(.).>>>importpogodb>>>db=pogodb.shellConnect("postgres://..dsn..")Connectionopened.Call`.close()`toclose.>>>db.insertOne({"_id":"foo","value":"foobar"})>>>db.findOne("foo"){'_id':'foo','value':'foobar'}>>>db.close()Connectioncommitted&closed.Call`.reopen()`toresume.>>>Note:pogodb.shellConnect(.)is meant only forquick and dirtyshell connections. You need to explicitly calldb.close()to commit the transaction and close the connection.Connecting ProperlyContext Manager:Usingwith pogodb.connect(.) as dbis a better way to connect. On exiting thewithblock, the transaction is auto-committed and the connection is auto-closed.importpogodbwithpogodb.connect("postgres://..dsn..")asdb:db.insertOne({"_id":"bar","value":"foobar"})# etc. ...Connection Decorator:For frequently connecting to the same database, consider setting up a connection decorator as follows:importpogodbdbConnect=pogodb.makeConnector("postgres://..dsn..")@dbConnectdefyourLogic(db):db.insertOne({"_id":"baz","value":"quax"})# etc. ...The decorator supplies thedbparameter to the decorated function. The parameter is supplied by name, so it must be calleddb, notmyDbor something else. That is,@dbConnectautomatically passesdbtoyourLogic, on each call.ParameterskipSetup:Bothpogodb.connect(.)andpogodb.makeConnector(.)acceptskipSetupas a parameter, which defaults toFalse. By default, PogoDB runs some setup-code upon each connection.Afteryour first interaction with the the database through  PogoDB, toavoidunnecessary setup, passskipSetup=True.Parameterverbose:Each connection method acceptsverboseas a parameter, defaulting toFalse. IfTrue, details regarding connecting to Postgres and executing SQL are printed usingprint(.).Inserting Data# Insert a single document:db.insertOne({"_id":"a","author":"Alice","text":"AA","rank":0,})# Insert multiple documents:db.insertMany([{"_id":"b","author":"Becci","text":"BB","rank":1},{"_id":"c","author":"Cathy","text":"CC","rank":2},{"_id":"d","author":"Alice","text":"DD","rank":1},]);Document Model:Each document must:be a JSON-serializabledictor dict-like object,andhave aunique stringvalue corresponding to the"_id"key.Retrieving DataIn continuation with the above code snippet ...# Find by _id:taskA=db.findOne("a");print(taskA.author,"-",taskA.text)# Output: Alice - AA# Find by sub-document:taskB=db.findOne({"author":"Becci"});print(taskB.author,"-",taskB.text)# Output: Becci - BB# Find multiple:aliceTasks=db.find({"author":"Alice"})assertaliceTasks[0]==taskAandlen(aliceTasks)==2taskD=aliceTasks[1];print(taskD.author,"-",taskD.text)# Output: Alice - DDNote: If no matching document is found,.findOne(.)returnsNonewhile.find(.)returns an empty list.Updating DataIn continuation with the above code snippet ...# Replace document:taskA.text="New AA"# <-- Update in-memorytaskA.x={"y":10,"z":20}db.replaceOne(taskA);# <-- Propagate to dbprint([db.findOne(taskA._id).text,taskA.x])# Output: ['New AA', {'y': 10, 'z': 20}]# Increment within document:db.incr({"_id":"a"},"x.y",1)# Incr x.y by 1print(db.findOne("a").x)# Output: {'y': 11, 'z': 20}# Decrement:db.decr({"_id":"a"},"x.z",1)# Decr x.z by 1print(db.findOne("a").x)# Output: {'y': 11, 'z': 19}Deleting DataIn continuation with the above code snippet ...# Delete by _id:db.deleteOne("a");print(db.findOne("a"))# Output: NoneAs of writing, you can only delete one document at a time, by_id.Quick PlugPogoDB built and maintained by the folks atPolydojo, Inc., led by Sumukh Barve. If your team is looking for a simple project management tool, please check out our latest product:BoardBell.com.Type IdentifiersPogoDB doesn't include buckets, collections or other such concepts forlogically groupingdifferent types of objects. But you can use a key for differentiating objects of various types.Convention:Keeping things simple, we recommend using the"type"key for indicating the type of a document/object.Example:In a blogging app, you'll have to deal with users, posts, comments and other types of object.# Insert users:db.insertMany([{"_id":"00","type":"user","name":"Alice"},{"_id":"01","type":"user","name":"Becci"},{"_id":"02","type":"user","name":"Cathy"},]);# Insert posts:db.insertMany([{"_id":"03","type":"post","authorId":"00","title":"Title X .. ","body":"Body X .."},{"_id":"04","type":"post","authorId":"01","title":"Title Y .. ","body":"Body Y .."},{"_id":"05","type":"post","authorId":"02","title":"Title Z .. ","body":"Body Z .."},{"_id":"06","type":"post","authorId":"00","title":"Title A .. ","body":"Body A .."},]);# Insert comments:db.insertMany([{"_id":"07","type":"comment","authorId":"02","postId":"03","text":"Comment P .."},{"_id":"08","type":"comment","authorId":"01","postId":"04","text":"Comment Q .."},{"_id":"09","type":"comment","authorId":"00","postId":"05","text":"Comment R .."},]);# Get all users:db.find({"type":"user"});# Get all posts:db.find({"type":"post"});# Get posts by a specific author:defgetPostByAuthor(userId):returndb.find({"type":"post","authorId":userId})# Get comments on a specific post:defgetCommentsByPost(postId):returndb.find({"type":"comment","postId":postId})# Get comments by a specific user:defgetCommentsByUser(userId):returndb.find({"type":"user","authorId":userId})As you can see, using the"type"key allows us to limit our query to a specific type. In continuation with the above code snippet, consider ...deftyped_getPostById(postId):returndb.findOne({"_id":postId,"type":"post"});defuntyped_getPostById(postId):returndb.findOne({"_id":postId})print(typed_getPostById("00"))# Correct result.# Output: Noneprint(untyped_getPostById("00"))# Weird result.# Output: {'_id': '00', 'name': 'Alice', 'type': 'user'}In the above example,"00"corresponds to Alice's"user"object. It's not a"post". Yetuntyped_getPostById(.)(incorrectly) returns it because it is type-blind.SQL FamiliarityFrom this point, the documentation assumes basic familiarity with SQL and Postgres' JSONB type. If you aren't familiar with these, you may safely skip most of the documentation below. However, please note that such familiarity would be required for running advanced, fine-grained queries.Under The HoodUnder the hood, PogoDB creates a single table namedpogotblwith a singleJSONBcolumn nameddoc(for document).When you calldb.find(.), PogoDB uses Postgres'@>to find and fetch the relevant documents. For example, callingdb.find({"type": "post"})will result in the following underlying SQL query:SELECTdocFROMpogotblWHEREdoc@>'{"type": "post"}';The above SQL will produce a list of records of typepsycopg2.extras.RealDictCursor, each with just one column:"doc". That is, the list of records is of the form:[{"doc":{"_id":"1..","type":"post",...}},{"doc":{"_id":"2..","type":"post",...}},...]After executing the SQL,db.find(.)plucks the"doc"column from each record and returns the resultant list, which is (as expected,) of the form:[{"_id":"1..","type":"post",...},{"_id":"2..","type":"post",...},...]Additionally,db.find(.)ensures that each returned document is a dot-accessible dictionary, thanks toDotsi. That is, you can use dot-notation (likepost._id) in addition to square-bracket notation (likepost["_id"]).CustomWHEREClauseLet's say you've stored the following exam-results using PogoDB:[{"_id":"1","studentId":"X","subjectId":"M","score":70},{"_id":"2","studentId":"Y","subjectId":"M","score":75},{"_id":"3","studentId":"Z","subjectId":"M","score":80},{"_id":"4","studentId":"X","subjectId":"N","score":85},{"_id":"5","studentId":"Y","subjectId":"N","score":90},{"_id":"6","studentId":"Z","subjectId":"N","score":95},]To findallresults forSubject M, we'd writedb.find({"subjectId": "M"}), which'd result in the underlying SQL query:SELECTdocFROMpogotblWHEREdoc@>'{"subjectId": "M"}';But how about retrievingonly thoseresults forSubject M, where thescoreis75or higher? In raw SQL, we could've written:SELECTdocFROMpogotblWHEREdoc@>'{"subjectId": "M"}'AND(doc->>'score')::int>=75;With regard to the two SQL queries above, note that theWHEREclause additionally includesAND (doc->>'score')::int >= 75. You can pass this extra bit todb.find(.)using thewhereEtcparameter:db.find({"subjectId":"M"},whereEtc="AND (doc->>'score')::int >= 75")In fact,db.find(.)is very flexible. Its full signature is documented below.Fulldb.find(.)Signaturedb.find(.)accepts 4 parameters:subdoc(required): The sub-document to match against.whereEtc(optional): Anything that should goafterPogoDB's  default SQLWHEREclause.argsEtc(optional): Tuple (or list) for placeholder-substitution againstwhereEtc.limit(optional): The maximum number of results desired. (Either use this param or add the SQLLIMITclause inwhereEtc; don't do both.)Note:db.findOne(.)has the same signature asdb.find(.), except of course, that it doesn't have alimitparameter (and neither does it expect to see theLIMITclause inwhereEtc).ORDER BY,LIMITEtc.Everything inwhereEtcis placed directly in the executed SQL. (Of course, placeholder-substitution is performed carefully. More on this later.) Thus, by usingwhereEtc, not only can you specify additional matching conditions (likeAND (doc->>'score')::int >= 75), but you can also include other SQL clauses such asORDER BY,LIMITetc.SORTING:Continuing the above exam-results example, to find results forSubject Msorted byStudent IDs(lowest to highest):db.find({"subjectId":"M"},whereEtc="ORDER BY doc->>'studentId' ASC")The underlying SQL executed by PogoDB will be:SELECTdocFROMpogotblWHEREdoc@>'{"subjectId": "M"}'ORDERBYdoc->>'studentId'ASC;LIMITING:To find thetop 2results forSubject M, we can use:db.find({"subjectId":"M"},whereEtc="ORDER BY (doc->>'score')::int DESC",limit=2)Or equivalently:db.find({"subjectId":"M"},whereEtc="ORDER BY (doc->>'score')::int DESC LIMIT 2")In either case, the underlying SQL executed by PogoDB will be:SELECTdocFROMpogotblWHEREdoc@>'{"subjectId": "M"}'ORDERBY(doc->>'score')::intDESCLIMIT2;PLACEHOLDERS:Let's write a function for finding the top N (n) results for a given subject (subjectId), at or above a given threshold (minScore).importpogodb;dbConnect=pogodb.makeConnector("postgres://..dsn..");@dbConnectdefgetTopN(n,subjectId,minScore,db):returndb.find({"subjectId":subjectId},whereEtc="AND (doc->>'score')::int >=%sORDER BY (doc->>'score')::int DESC",argsEtc=[minScore],limit=n);Alternatively:@dbConnectdefgetTopN(n,subjectId,minScore,db):returndb.find({"subjectId":subjectId},whereEtc="AND (doc->>'score')::int >=%sORDER BY (doc->>'score')::int DESC LIMIT%s",argsEtc=[minScore,n],);Note: Placeholder substitution is deferred toPsycopg'scursor.execute(.)method, which should preventSQL-injection.Warning:DoNOTuse string concatenation (i.e.+,str.join(.), etc.) or string interpolation (i.e.%,str.format(.),  etc.) along withwhereEtc. PassargsEtcinstead.Executing Raw SQLIf you'd like to execute raw SQL, we recommend usingPsycopgdirectly. We recommendagainstusingdb._execute(.).Typically,db._execute(.)should only be relevant to PogoDB's maintainers. It accepts three parameters:stmt(required): The SQL statement to be executed.args(optional): Tuple (or list) for%splaceholder substitution.fetch(optional): EitherNone(optional),"one"or"all".Parametersstmtandargsare passed directly to Psycopg'scursor.execute(.)method. Based onfetch, none, one or all records are fetched.A close cousin todb._execute(.)isdb._findSql(.), which is useful for executingSELECTqueries. It only acceptsstmt(required) andargs(optional), as described above. It fetches all matching results, plucks thedoccolumn, ensures dot-accessibility of dictionary objects, and returns the result.LicensingCopyright (c) 2020 Polydojo, Inc.Software Licensing:The software is released "AS IS" under theApache License 2.0, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Kindly seeLICENSE.txtfor more details.No Trademark Rights:The above software licensing termsdo notgrant any right in the trademarks, service marks, brand names or logos of Polydojo, Inc.