Package: brain
ContentsIntroductionQuick startBasic functionsTransaction supportXML RPC layerReferenceKnown limitationsPathExceptionsEnginesTestsbrain.connect()brain.getDefaultEngineTag()brain.getEngineTags()brain.opConnection, RemoteConnectionConnection.begin()Connection.beginAsync()Connection.beginSync()Connection.close()Connection.commit()Connection.create()Connection.delete(), Connection.deleteMany()Connection.dump()Connection.getRemoveConflicts()Connection.insert(), Connection.insertMany()Connection.modify()Connection.objectExists()Connection.read(), Connection.readByMask, Connection.readByMasks()Connection.repair()Connection.rollback()Connection.search()CachedConnectionClientClient.connect()Client.getDefaultEngineTag()Client.getEngineTags()ServerServer.start()Server.stop()IntroductionDocument databases may prove out to be more convenient than relational ones for programs
that operate with a large set of objects with different parameters. With document database
you do not need to develop the database scheme, just toss objects with complex structure
to the database and retreive them by any search criteria. For example, this approach may
be useful for music players (store tracks/albums/artists as objects with cross-references
and references to real files), project management software, bug tracking systems and so on.This package is, in effect, a wrapper, which makes the relational database engine look
like DDB. Of course, this approach has its drawbacks - noticeable overhead and slow store/retreive
operations. But it gives fast search, transaction support and all features of chosen relational
DB engine (like DB server, secure access, replication and other stuff) for free. Plus, all DB engines
are constantly improving without my attention - isn’t it cool?This package is in beta state now. You can find all planned tasks in todo.rst (not included in
distribution, get it from master branch). I will appreciate any comments and bug reports,
from grammar and spelling errors in documentation to flaws in module architecture.Quick startThis section will show only the simple usage examples. For exhaustive information please consult
theReferenceentries for corresponding functions.Basic functionsFirst, import the module:>>> import brainThen we will need to connect to existing database or create the new one.
In this example we will use the default DB engine (sqlite3) and in-memory database.
First None stands for so called “engine tag” (which identifies DB engine to use),
and the second one stands for DB name (which is mandatory for sqlite3 engine).>>> conn = brain.connect(None, None)Now we can create some objects. Objects are identified by their IDs, which are
intended to be opaque. The only thing that the end user should know is that they
can be stored in database too.>>> id1 = conn.create({'a': 1, 'b': 1.345})
>>> id2 = conn.create({'id1': id1, 'list': [1, 2, 'some_value']})These objects can be read from database:>>> data1 = conn.read(id1)
>>> print(data1)
{'a': 1, 'b': 1.345}
>>> data2 = conn.read(id2)
>>> print(data2)
{'list': [1, 2, 'some_value'], 'id1': 1}You can see that the object ID is, in fact, a simple integer. It is true for sqlite3 engine,
but each engine can use its own ID format.The next function isConnection.modify(); it allows us to change the contents of the object.>>> conn.modify(id1, ['a'], 2)
>>> data1 = conn.read(id1)
>>> print(data1)
{'a': 2, 'b': 1.345}Its first argument is object ID, second one is is thepathto some place inside object and
the third one is the value to store (can be either some simple type or data structure). Path
is a list, whose elements can be strings, integers or Nones. String element corresponds to key
in dictionary, integer to list index, and None to list mask.You may have noticed that the second object contains a list. New elements can be added
to list in two ways - either usingConnection.modify()with path, specifying list index to create,
or inserting new element to some place in list:>>> conn.modify(id2, ['list', 3], 3)
>>> print(conn.read(id2))
{'list': [1, 2, 'some_value', 3], 'id1': 1}
>>> conn.insert(id2, ['list', 0], 4)
>>> print(conn.read(id2))
{'list': [4, 1, 2, 'some_value', 3], 'id1': 1}
>>> conn.insert(id2, ['list', None], 5)
>>> print(conn.read(id2))
{'list': [4, 1, 2, 'some_value', 3, 5], 'id1': 1}First action creates the element with index 3 in list; note that it is expanded automatically.
Second action inserts the new element to the beginning of the list. Third action inserts
the new element to the end of the list.We can now search for objects in database. For example, we want to find the object, which
has list under ‘list’ key in dictionary, which, in turn has the first element equal to 4.>>> import brain.op as op
>>> objs = conn.search(['list', 0], op.EQ, 4)
>>> print(objs == [id2])
TrueSearch request supports nested conditions and several types of comparisons (including regexps).
See its reference page for more information.The last basic function isConnection.delete(). It can delete the whole objects, or its parts
(dictionary keys or list elements).>>> print(conn.objectExists(id1))
True
>>> conn.delete(id1)
>>> print(conn.objectExists(id1))
False
>>> conn.delete(id2, ['list'])
>>> print(conn.read(id2))
{'id1': 1}
>>> conn.close()Connection should be closed usingConnection.close()after it is not longer needed. In case of
in-memory database, of course, all data will be lost after call toConnection.close().Transaction supportOne of the main advantages of using the developed SQL engine as a back-end is the
ACID compatibility. As a result, brain front-end has full transaction support too.If transaction was not started explicitly, the new one is created and committed for
each request (create, modify, insert and so on) implicitly. In case of some underlying
error, this transaction is rolled back, so the request cannot be completed partially.There are two types of transactions - synchronous and asynchronous. During the
synchronous transaction you get request results instantly; during the asynchronous one
requests do not return any results - all results are returned byConnection.commit()as a list.Let’s illustrate this by several simple examples. First, connect to database and
create some objects.>>> import brain
>>> conn = brain.connect(None, None)
>>> id1 = conn.create({'a': 1, 'b': 2})
>>> id2 = conn.create({'c': 3, 'd': 4})For each of twoConnection.create()’s above transactions were started and committed implicitly
(because there were not any active transactions at the moment). Now we will create synchronous
transaction explicitly:>>> conn.beginSync()
>>> conn.modify(id1, ['a'], 10)
>>> print(conn.read(id1))
{'a': 10, 'b': 2}
>>> conn.commit()
>>> print(conn.read(id1))
{'a': 10, 'b': 2}Note that during synchronous transaction modifications become visible instantly. Now
consider the similar operation inside a transaction, but this time we will roll it back:>>> conn.beginSync()
>>> conn.modify(id1, ['a'], 20)
>>> print(conn.read(id1))
{'a': 20, 'b': 2}
>>> conn.rollback()
>>> print(conn.read(id1))
{'a': 10, 'b': 2}Like in the previous example, modification instantly becomes visible, but after the rollback
it is gone.Asynchronous transactions are slightly different. During the transaction requests will not
return values, because they are not, in fact, executed - they are stored inside the connection
object and passed to DB engine in one single package whenConnection.commit()is called. If the user
changes his mind and callsConnection.rollback(), all this package is simply discarded.>>> conn.beginAsync()
>>> conn.modify(id1, ['a'], 0)
>>> conn.read(id1)
>>> print(conn.commit())
[None, {'a': 0, 'b': 2}]
>>> conn.close()In the example above there were two requests inside a transaction; first one,Connection.modify()does not return anything, and the second one,Connection.read(), returned object contents.
ThereforeConnection.commit()returned both their results as a list.XML RPC layerBrain has embedded XML RPC server and client. First, we will create and start server:>>> import brain
>>> srv = brain.Server()
>>> srv.start()Now server is active on localhost, port 8000 (by default). It is executed in its own thread,
sostart()returns immediately. If you enterhttp://localhost:8000in your browser, you
will get a page with list of functions the server supports.Then we should create the client - either in this session, in other process or even on
the other computer:>>> cl = brain.Client('http://localhost:8000')And client object gives us the ability to create connections. The format of itsconnect()method is the same as forbrain.connect():>>> conn = cl.connect(None, None)This object behaves exactly the same as theConnectionobject returned bybrain.connect().
You can try all examples from previous sections - they all should work. In the end you
should close the connection and stop server:>>> conn.close()
>>> srv.stop()Unlikestart(),stop()waits for server to shut down.ReferenceKnown limitationsValue limitations:Currently the following Python types are supported:None,int,float,strandbytes.Integers are limited to 8 bytes (by DB engines) and to 4 bytes by XML RPC protocol.Structure limitations:Each object can contain arbitrarily combined values, lists and dictionaries.Structure depth is not limited theoretically, but in practice it is - by DB engine.Lists and dictionaries can be empty.Dictionary keys should have string type.PathPath to some value in object is a list, which can contain only strings, integers and Nones.
Empty list means the root level of an object; string stands for dictionary key and integer
stands for position in list. None is used in several special cases: to specify thatConnection.insert()should perform insertion at the end of the list or as a mask forConnection.delete()andConnection.read().String elements must not contain uppercase symbols. This is done because each field name
correspond to table name in underlying SQL engine, and some engines ignore case in table names.
So, in order to avoid later search errors, uppercase symbols in field names are simply not allowed -FormatErroris thrown.If path does not contain Nones, it is calleddetermined.Example:>>> conn = brain.connect(None, None)
>>> id1 = conn.create({'tracks': [{'name': 'track 1', 'length': 240},
... {'name': 'track 2', 'length': 300}]})
>>> print(conn.read(id1, ['tracks', 0, 'name']))
track 1
>>> print(conn.readByMask(id1, ['tracks', None, 'length']))
{'tracks': [{'length': 240}, {'length': 300}]}
>>> conn.close()ExceptionsFollowing exceptions can be thrown by API:brain.FacadeError:Signals about the error in high-level wrappers. Can be caused by incorrect
calls toConnection.begin()\Connection.commit()\Connection.rollback(),
incorrect engine tag and so on.brain.EngineError:Signals about an error in DB engine wrapper.brain.StructureError:Signals about error in object/database structure - for example, conflicting fields.brain.FormatError:Wrong format of supplied data: path is not a list, or have elements of wrong type,
data has values of wrong type and so on.EnginesCurrently two engines are supported:sqlite3:SQLite 3 engine, built in Python 3.Arguments:(name, open_existing=None, db_path=None)name:Database file name. If equal toNone, in-memory database is created.open_existing:Ignored ifnameis equal to None.If equal to True, existing database file will be opened orEngineErrorwill be raised if it does not exist.If equal to False, new database file will be created (in place of the existing one, if
necessary)If equal to None, existing database will be opened or the new one will be created, if
the database file does not exist.db_path:If is not None, will be concatenated (using platform-specific path join) withnamepostgre:Postgre 8 engine. Will be used ifpy-postgresqlis installed.Arguments:(name, open_existing=None,host='localhost',port=5432,user='postgres',password='',connection_limit=-1)name:Database name.open_existing:Same logic as for SQLite3 enginehost:Postgre server nameport:Postgre server portuser,password:Credentials for connecting to Postgre serverconnection_limit:Connection limit for newly created database. Unlimited by default.TestsThe package is supplied with a set of functionality tests which I use for debugging purposes.
They can be found intestsubfolder of module main folder. Tests are executed usingrun.py,
which has the following parameters:run.py <func|fuzz|doc|perf> [options]func:Functionality tests. They are based on Python’sunittestmodule, with some minor extensions.
Currently they provide almost 100% coverage of package code.fuzz:Several objects with random data are created and random actions (Connection.modify(),Connection.insert(),Connection.read(),Connection.delete()) are performed on them.
After each action result is compared to   the result ofFakeConnection, which uses Python
data structures to emulate package behavior.doc:Test examples in this documentation using Python’sdoctestmodule.perf:Simple non-atomic performance tests (measuring times offunctests plus combining
results offuzztests with several predefined seeds).global parameters:-vLEVEL,--verbosity=LEVEL:Integer from 0 (less verbose) to 3 (more verbose), specifying the amount of information
which is displayed during tests. Default is 2.Fordoctests verbosity level 3 and above enablesverbose=Truemode (which means
that all tests will be shown); otherwise only errors will be shown.funcparameters:--ae,--all-engines:If specified, all available DB engines will be tested. If not specified, only the default
engine (seegetDefaultEngineTag()) will be tested.--ac,--all-connections:If specified, all available connections will be tested (local, XML RPC and so on). If
not specified, only local connection will be tested.--as,--all-storages:If specified, all storage types for each engine will be tested (for example, for sqlite3
available types are in-memory and file). If not specified, only the default storage for
each engine will be tested.fuzzparameters:-oNUM,--objects=NUM:Number of object to be tested simultaneously. Default is 1.-aNUM,--actions=NUMNumber of actions to be performed for one object. Default is 100.-sSEED,--seed=SEEDInteger which will be used as starting seed for random number generator. This wil allow
to get reproduceable results. By default, random seed is generated.brain.connect()Connect to the database (or create the new one).Arguments:connect(engine_tag, *args, remove_conflicts=False, **kwds)engine_tag:String, specifying the DB engine to use. Can be obtained bygetEngineTags().
If equal toNone, the default tag is used; its value can be obtained usinggetDefaultEngineTag().remove_conflicts:Default value of this parameter forConnection.modify()andConnection.insert().args,kwds:Engine-specific parameters. SeeEnginessection for further information.Returns:Connectionobject.brain.getDefaultEngineTag()Get engine tag, which will be used ifNoneis specified as engine tag inconnect().Arguments:getDefaultEngineTag()Returns: default engine tag.brain.getEngineTags()Get available engine tags.Arguments:getEngineTags()Returns: list of engine tags.brain.opThis submodule contains operator definitions forConnection.search()request:inversion operatorNOT- can be used in all conditions.logical operatorsORandAND- can be used to link simple conditions.comparison operatorsEQ(equal to),REGEXP,LT(lower than),LTE(lower than or equal to),GT(greater than) andGTE(greater than or equal to) - can be used in simple conditions.EQcan be used for all value types.REGEXPcan be used only for strings. It should support POSIX regexps.LT,LTE,GTandGTEcan be used for integers and floats.Connection, RemoteConnectionThese objects represent the connection to the database. They have exactly the same public interface,
so only Connection methods will be described.Currently the following connection methods are available:Connection.begin()Connection.beginAsync()Connection.beginSync()Connection.close()Connection.commit()Connection.create()Connection.delete()Connection.deleteMany()Connection.dump()Connection.getRemoveConflicts()Connection.insert()Connection.insertMany()Connection.modify()Connection.objectExists()Connection.read()Connection.readByMask()Connection.readByMasks()Connection.repair()Connection.rollback()Connection.search()Connection.begin()Start database transaction. If transaction is already in progress,FacadeErrorwill be raised.Arguments:begin(sync)sync:Boolean value, specifying whether transaction should be synchronous or not
(seeConnection.beginSync()orConnection.beginAsync()correspondingly for details)Example:Start new transaction>>> conn = brain.connect(None, None)
>>> conn.begin(sync=True)Failed attempt to start transaction when another one is in progress>>> conn.begin(sync=True)
Traceback (most recent call last):
...
brain.interface.FacadeError: Transaction is already in progress
>>> conn.close()Connection.beginAsync()This function is an alias forConnection.begin()(equals tobegin(sync=False))Start asynchronous transaction. During the asynchronous transaction requests to database
are not processed, just stored inside the connection. Correspondingly, actual database
transaction is not started. WhenConnection.commit()is called, database transaction is created,
and all of requests are being processed at once, and their results are returned fromConnection.commit()as a list.This decreases the time database is locked by the transaction and increases the speed
of remote operations (one XML RPC multicall is faster than several single calls).
But, of course, this method is less convenient than the synchronous
or implicit transaction.Arguments:beginAsync()Example:>>> conn = brain.connect(None, None)
>>> id1 = conn.create({'name': 'Bob'})
>>> conn.beginAsync()
>>> conn.modify(id1, ['name'], 'Carl')
>>> print(conn.read(id1))
None
>>> print(conn.commit())
[None, {'name': 'Carl'}]
>>> conn.close()Connection.beginSync()This function is an alias forConnection.begin()(equals tobegin(sync=True))Start synchronous transaction. During the synchronous transaction request results are available
instantly (for the same connection object), so one can perform complex actions inside
one transaction. On the downside, actual database transaction is opened all the time,
probably locking the database (depends on the engine). In case of remote connection,
synchronous transaction means that there will be several requests/responses performed,
slowing down transaction processing.Arguments:beginSync()Example:>>> conn = brain.connect(None, None)
>>> id1 = conn.create({'name': 'Bob'})
>>> conn.beginSync()
>>> conn.modify(id1, ['name'], 'Carl')
>>> print(conn.read(id1))
{'name': 'Carl'}
>>> conn.commit()
>>> conn.close()Connection.close()Close connection to the database. All uncommitted changes will be lost.Arguments:close()Connection.commit()Commit current transaction. If transaction is not in progress,FacadeErrorwill be raised.Arguments:commit()Example:Create and commit transaction>>> conn = brain.connect(None, None)
>>> conn.beginSync()
>>> conn.commit()Try to commit non-existent transaction>>> conn.commit()
Traceback (most recent call last):
...
brain.interface.FacadeError: Transaction is not in progress
>>> conn.close()Connection.create()Create new object in database.Arguments:create(self, data, path=None)data:Initial object contents. Can be either a value of allowed type, list or dictionary.path:If defined, specifies thepathwheredatawill be stored (if equal toNone,
data is stored in root). Should be determined.Returns: object IDExample:Creation without path>>> conn = brain.connect(None, None)
>>> id1 = conn.create([1, 2, 3])
>>> print(conn.read(id1))
[1, 2, 3]Creation with path>>> id2 = conn.create([1, 2, 3], ['key'])
>>> print(conn.read(id2))
{'key': [1, 2, 3]}
>>> conn.close()Connection.delete(), Connection.deleteMany()Delete the whole object or some of its fields. If an element of list is deleted,
other list elements are shifted correspondingly.Arguments:delete(id, path=None)deleteMany(id, paths=None)Note:delete(id, path)is an alias fordeleteMany(id, [path])id:Target object ID.paths:List ofpaths. If given, is used as the set of masks, specifying fields to delete.
IfNone, the whole object will be deleted.Example:Deletion of the whole object>>> conn = brain.connect(None, None)
>>> id1 = conn.create([1, 2, 3])
>>> conn.delete(id1)
>>> print(conn.objectExists(id1))
FalseDeletion of specific field>>> id1 = conn.create([1, 2, 3])
>>> conn.delete(id1, [1])
>>> print(conn.read(id1))
[1, 3]Deletion by mask>>> id1 = conn.create({'tracks': [{'name': 'track 1', 'length': 240},
... {'name': 'track 2', 'length': 300}]})
>>> conn.delete(id1, ['tracks', None, 'length'])
>>> print(conn.read(id1))
{'tracks': [{'name': 'track 1'}, {'name': 'track 2'}]}
>>> conn.close()Connection.dump()Get all database contents.Arguments:dump()Returns: list [object 1 ID, object 1 contents, object 2 ID, object 2 contents, …]Example:>>> conn = brain.connect(None, None)
>>> id1 = conn.create([1, 2, 3])
>>> id2 = conn.create({'key': 'val'})
>>> print(conn.dump())
[1, [1, 2, 3], 2, {'key': 'val'}]
>>> conn.close()Connection.getRemoveConflicts()Get current default value ofremove_conflictskeyword (the one which was set
when connection was created, inconnect()).Arguments:getRemoveConflicts()Returns: True or FalseConnection.insert(), Connection.insertMany()Insert given data to list in object.Arguments:insert(id, path, value, remove_conflicts=None)insertMany(id, path, values, remove_conflicts=None)Note:insert(id, path, value, remove_conflicts)is an alias forinsert(id, path, [value], remove_conflicts)id:Target object ID.path:Pathto insert to. Should point to list element (i.e., end with integer orNone) and
be determined (except for, probably, the last element). If the last element isNone,
insertion will be performed to the end of the list.value:Data to insert - should be a supported data structure.remove_conflictsSee the description of this parameter forConnection.modify().insert()tries to performmodify(id, path, [], remove_conflicts)before doing any actions.Remarks:If target object does not have the field, whichpathis pointing to, it will be created.Ifpathpoints to dictionary key,FormatErrorwill be raised.If dictionary already exists at the place whichpathis pointing to,StructureErrorwill be raised.Example:>>> conn = brain.connect(None, None)
>>> id1 = conn.create({'key': [1, 2, 3]})Insertion to the beginning>>> conn.insert(id1, ['key', 0], 0)
>>> print(conn.read(id1))
{'key': [0, 1, 2, 3]}Insertion to the end>>> conn.insert(id1, ['key', None], 4)
>>> print(conn.read(id1))
{'key': [0, 1, 2, 3, 4]}Autovivification, no conflicts>>> conn.insert(id1, ['key2', None], 50)
>>> print(conn.read(id1))
{'key2': [50], 'key': [0, 1, 2, 3, 4]}Autovivification raises error on existing conflicts>>> conn.insert(id1, ['key2', 'key3', None], 50)
Traceback (most recent call last):
...
brain.interface.StructureError: Path ['key2', 'key3'] conflicts with existing structureAutovivification, remove conflicts>>> conn.insert(id1, ['key2', 'key3', None], 50, remove_conflicts=True)
>>> print(conn.read(id1))
{'key2': {'key3': [50]}, 'key': [0, 1, 2, 3, 4]}Insert several values at once>>> conn.insertMany(id1, ['key2', 'key3', None], [51, 52, 53])
>>> print(conn.read(id1))
{'key2': {'key3': [50, 51, 52, 53]}, 'key': [0, 1, 2, 3, 4]}Insert data structure>>> conn.insert(id1, ['key2', 'key3', None], {'subkey': 'val'})
>>> print(conn.read(id1))
{'key2': {'key3': [50, 51, 52, 53, {'subkey': 'val'}]}, 'key': [0, 1, 2, 3, 4]}Try to pass wrong path to insert()>>> conn.insert(id1, ['key2', 'key3'], 'val')
Traceback (most recent call last):
...
brain.interface.FormatError: Last element of target field name should be None or integer
>>> conn.close()Connection.modify()Modify or create field in object.Arguments:modify(id, path, value, remove_conflicts=None)id:Target object ID.path:Path where to store data.value:Data to save at target path.remove_conflicts:Determines the way conflicts ofpathwith existing data structure are handled. Possible conflicts are:pathpoints to dictionary, when list already exists on the same levelpathpoints to list, when dictionary already exists on the same levelpathpoints to list or dictionary, when scalar value already exists on the same levelIfremove_conflictsequalsTrue, all conflicting fields are deleted. In other words,modify()is guaranteed to finish successfully and the result ofread(id, path)is
guaranteed to be equal tovalue.Ifremove_conflictsequalsFalse,StructureErroris raised if conflict is found.Ifremove_conflictsequals None (default), the value given tobrain.connect()is used.Example:>>> conn = brain.connect(None, None)
>>> id1 = conn.create({'key': 'val'})Simple modification>>> conn.modify(id1, ['key'], 'new_val')
>>> print(conn.read(id1))
{'key': 'new_val'}Save data structure in place of value>>> conn.modify(id1, ['key'], [1, 2])
>>> print(conn.read(id1))
{'key': [1, 2]}Try to autovivify conflicting path withoutremove_conflictsset>>> conn.modify(id1, ['key', 'key2'], 'val')
Traceback (most recent call last):
...
brain.interface.StructureError: Path ['key', 'key2'] conflicts with existing structureImplicitly transform list remove[1, 2]usingremove_conflicts>>> conn.modify(id1, ['key', 'key2'], 'val', remove_conflicts=True)
>>> print(conn.read(id1))
{'key': {'key2': 'val'}}
>>> conn.close()Connection.objectExists()Check if object with given ID exists.Arguments:objectExists(id)id:Object ID.Returns: True if object with given ID exists, False otherwise.Connection.read(), Connection.readByMask, Connection.readByMasks()Read contents of given object.Arguments:read(id, path=None, masks=None)readByMask(id, mask=None)readByMasks(id, masks=None)Note:readByMask(id, mask)is an alias forreadByMasks(id, [mask])andreadByMasks(id, masks), in turn, is an alias forread(id, None, masks).id:Target object ID.path:Pathto read from. Read from root by default.masks:List ofpaths; all results which do not have one of them in the beginning, will be filtered out.
Masks are relative topath.Returns: resulting data structure.Example:>>> conn = brain.connect(None, None)
>>> id1 = conn.create({'tracks': [{'name': 'track 1', 'length': 240}, {'name': 'track 2', 'length': 300}]})Read the whole object>>> print(conn.read(id1))
{'tracks': [{'length': 240, 'name': 'track 1'}, {'length': 300, 'name': 'track 2'}]}Read from given path>>> print(conn.read(id1, ['tracks', 0]))
{'length': 240, 'name': 'track 1'}Read by mask>>> print(conn.readByMask(id1, ['tracks', None, 'length']))
{'tracks': [{'length': 240}, {'length': 300}]}Read from path, filter by mask. Note that mask is relative.>>> print(conn.read(id1, ['tracks'], [[None, 'length']]))
[{'length': 240}, {'length': 300}]
>>> conn.close()Connection.repair()Internal database structure includes some redundant tables, which are used to increase
database performance. This function can restore them based on actual field data stored in
database. It can be used when database requests (evenConnection.read()) are returning strange
errors with long call stack. These internal tables can be spoiled either by errors in logic
or because of some errors in underlying SQL engine.Arguments:repair()Connection.rollback()Roll current transaction back. If transaction is not in progress,FacadeErrorwill be raised.Arguments:rollback()Example:Create and rollback transaction>>> conn = brain.connect(None, None)
>>> conn.beginSync()
>>> conn.rollback()Try to rollback non-existent transaction>>> conn.rollback()
Traceback (most recent call last):
...
brain.interface.FacadeError: Transaction is not in progress
>>> conn.close()Connection.search()Search for objects in database which satisfy given conditions.Arguments:search(condition)condition:One of three possibilities:ListconditionTupleconditionEmpty list (only at root level, cannot be a part of condition)Simpleconditionis a list [brain.op.NOT, ]path, comparison_operator, value; complexconditionis a list [brain.op.NOT, ]condition, [[logical_operator,
[brain.op.NOT, ]condition, ] … ], where eachconditioncan be either simple or complex.On the root level, you may not wrap condition in a list, but rather just pass
it as a tuple of arguments to function.Logical_operator and comparison_operator - anyoperators. Value should be a
scalar of supported type. Note that different values support different type of
comparisons; seebrain.opreference for details.Ifconditionis an empty list, it matches all existing object IDs in database.If condition uses path, not existing in some object, condition is considered
to be false for this object if it does not containbrain.op.NOTand true
otherwise.Compound conditions are evaluated successively:[cond1, op1, cond2, op2, cond3]is evaluated
as[[cond1, op1, cond2], op2, cond3].In compound conditionsNOTapplies to the condition next to it:[NOT, cond1, op1, cond2, op2, NOT, cond3]is evaluated as[[[NOTcond1], op1, cond2], op2, [NOT, cond3]].Returns: list of object IDs, satisfying given conditions (note that order can
depend on DB engine).Example:>>> import brain.op as op
>>> conn = brain.connect(None, None)
>>> id1 = conn.create({'name': 'Alex', 'age': 22})
>>> id2 = conn.create({'name': 'Bob', 'height': 180, 'age': 25})
>>> id3 = conn.create({'name': 'Carl', 'height': 170, 'age': 26})Empty condition>>> print(set(conn.search()) == set([id1, id2, id3]))
TrueSimple condition>>> print(conn.search(['name'], op.EQ, 'Alex') == [id1])
TrueCompound condition>>> print(set(conn.search([['name'], op.EQ, 'Alex'], op.OR,
... [['name'], op.EQ, 'Carl'])) == set([id1, id3]))
TrueCompound condition with negative>>> print(set(conn.search([['name'], op.EQ, 'Alex'], op.OR,
... [op.NOT, ['name'], op.EQ, 'Carl'])) == set([id1, id2]))
TrueCondition with non-equality>>> print(conn.search(['age'], op.GT, 25) == [id3])
TrueCondition with non-existent field>>> print(conn.search([['name'], op.EQ, 'Alex'], op.AND,
... [['height'], op.EQ, 180]) == [])
TrueCondition with non-existent field and negative>>> print(conn.search([['name'], op.EQ, 'Alex'], op.AND,
... [op.NOT, ['height'], op.EQ, 180]) == [id1])
TrueLong compound condition>>> print(conn.search([['name'], op.EQ, 'Alex'], op.OR,
... [['age'], op.EQ, 25], op.AND,
... [['height'], op.GT, 175]) == [id2])
True
>>> conn.close()CachedConnectionThis class wraps anything withConnection-like interface and adds object caching.
The caching algorithm is rather simple, it speeds up only read operations (by keeping
copies of objects in memory).Warning: This class can work incorrectly if more than one connection to database is opened.
For example, if the second connection changes something in database, the cache will not change
and, therefore, read operation from the first connection will return the old value.Arguments:CachedConnection(conn, size_threshold=0)conn:Object withConnectioninterface.size_threshold:How many objects the cache must keep in memory. If zero, all accessed objects are kept.
If non-zero, specifies the number of most recently accessed object kept.ClientXML RPC client for brain DB. Based on Python’s built-inxmlrpc.client.ServerProxyand has the
following extensions:Supports keyword arguments to calls (adds dictionary with keyword argument to each method call)Unmarshalls knownexceptionsfromFaultsreturned by serverArguments:Client(addr)addr:Address to connect to.Client.connect()Connect to DB or create a new one.Arguments: same as forbrain.connect().Returns:RemoteConnectionobject.Client.getDefaultEngineTag()Same asbrain.getDefaultEngineTag().Client.getEngineTags()Same asbrain.getEngineTags().ServerXML RPC server for database. Based on Python’s built-inxmlrpc.server.DocXMLRPCServerand extends standard XML RPC slightly: it supports keyword arguments to calls.
They are passed as the dictionary in additional argument to each function.
If function does not have any keyword arguments, empty dictionary is passed.Arguments:Server(port=8000, name=None, db_path=None)port:Port where server will wait for requests.name:Server thread name.db_path:Will be used with DB engines, which store information in files -db_pathwill serve as
a prefix to each created DB file.Server.start()Start server in a separate thread. Returns instantly.Arguments:start()Server.stop()Shutdown server and wait for its thread to stop.Arguments:stop()